diff --git a/.travis.yml b/.travis.yml index 02f303b..d131fe2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,8 @@ before_install: env: global: - alpine_image='alpine:3.8' - - latest='recent' - repo='jfloff/alpine-python' + - latest='3.8' matrix: - version='2.7' type='' - version='2.7' type='-onbuild' @@ -20,6 +20,12 @@ env: - version='3.6' type='' - version='3.6' type='-onbuild' - version='3.6' type='-slim' + - version='3.7' type='' + - version='3.7' type='-onbuild' + - version='3.7' type='-slim' + - version='3.8' type='' + - version='3.8' type='-onbuild' + - version='3.8' type='-slim' before_script: - sudo docker pull $alpine_image diff --git a/2.7-onbuild/Dockerfile b/2.7-onbuild/Dockerfile index 4304a42..c374ebc 100644 --- a/2.7-onbuild/Dockerfile +++ b/2.7-onbuild/Dockerfile @@ -1,41 +1,64 @@ -FROM alpine:3.8 +FROM alpine:3.11 # VERSIONS -ENV ALPINE_VERSION=3.8 \ - PYTHON_VERSION=2.7.15 +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=2.7.16 # PATHS -ENV PYTHON_PATH=/usr/lib/python$PYTHON_VERSION \ - PATH="/usr/lib/python$PYTHON_VERSION/bin/:${PATH}" +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. + # * dumb-init: a proper init system for containers, to reap zombie children + # * musl: standard C library + # * lib6-compat: compatibility libraries for glibc + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * build-base: used so we include the basic development packages (gcc) + # * bash: so we can access /bin/bash + # * git: to ease up clones of repos + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + musl \ + libc6-compat \ + linux-headers \ + build-base \ + bash \ + git \ + ca-certificates \ + libressl-dev \ + tzdata \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + git \ + " -# PACKAGES -# * dumb-init: a proper init system for containers, to reap zombie children -# * musl: standard C library -# * lib6-compat: compatibility libraries for glibc -# * linux-headers: commonly needed, and an unusual package name from Alpine. -# * build-base: used so we include the basic development packages (gcc) -# * bash: so we can access /bin/bash -# * git: to ease up clones of repos -# * ca-certificates: for SSL verification during Pip and easy_install -ENV PACKAGES="\ - dumb-init \ - musl \ - libc6-compat \ - linux-headers \ - build-base \ - bash \ - git \ - ca-certificates \ -" - -# PACKAGES needed to built python -ENV PYTHON_BUILD_PACKAGES="\ - readline-dev \ - zlib-dev \ - bzip2-dev \ - sqlite-dev \ - openssl-dev \ -" +# Copy in the entrypoint script -- this installs prerequisites on container start. +COPY entrypoint.sh /entrypoint.sh RUN set -ex ;\ # find MAJOR and MINOR python versions based on $PYTHON_VERSION @@ -56,32 +79,36 @@ RUN set -ex ;\ # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ # use pyenv to download and compile specific python version - git clone --depth 1 https://github.com/pyenv/pyenv /usr/lib/pyenv ;\ - PYENV_ROOT=/usr/lib/pyenv /usr/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ - # move specific version to correct path delete pyenv, no longer needed - mv /usr/lib/pyenv/versions/$PYTHON_VERSION/ $PYTHON_PATH ;\ - rm -rfv /usr/lib/pyenv ;\ - # change the path on the header of every file from PYENV_ROOT to PYTHON_PATH - cd $PYTHON_PATH/bin/ && sed -i "s+/usr/lib/pyenv/versions/$PYTHON_VERSION/+$PYTHON_PATH/+g" * ;\ - # delete binary "duplicates" and replace them with symlinks - # this also optimizes space since they are actually the same binary - rm -f $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION \ - $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION \ - $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION-config \ - $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION-config ;\ - ln -sf $PYTHON_PATH/bin/python $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION ;\ - ln -sf $PYTHON_PATH/bin/python $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION ;\ - ln -sf $PYTHON_PATH/bin/python-config $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION-config ;\ - ln -sf $PYTHON_PATH/bin/python-config $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION-config ;\ + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ # delete files to to reduce container size # tips taken from main python docker repo - find /usr/lib/python$PYTHON_VERSION -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # set timezone info + ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime ;\ # remove build dependencies and any leftover apk cache apk del --no-cache --purge .build-deps ;\ - rm -rf /var/cache/apk/* - -# Copy in the entrypoint script -- this installs prerequisites on container start. -COPY entrypoint.sh /entrypoint.sh + rm -rf /var/cache/apk/* ;\ + # set proper permission to run entrypoint script + chmod a+x /entrypoint.sh # install requirements # this way when you build you won't need to install again @@ -94,4 +121,4 @@ ONBUILD RUN rm -f /requirements.installed # since we will be "always" mounting the volume, we can set this up ENTRYPOINT ["/usr/bin/dumb-init"] -CMD ["python"] \ No newline at end of file +CMD ["python"] diff --git a/2.7-onbuild/entrypoint.sh b/2.7-onbuild/entrypoint.sh index 901491a..50c2ab1 100755 --- a/2.7-onbuild/entrypoint.sh +++ b/2.7-onbuild/entrypoint.sh @@ -12,7 +12,7 @@ VERBOSITY=1 TMP_REQFILE='/tmp/requirements.txt' function usage () { - echo <<"EOF" + cat <<"EOF" Usage: $0 [-a -b -p -A -B -P -r -q -x] [--] -a : APK requirement. Can be specified multiple times. -b : APK build requirement. These will be removed at the end to save space. @@ -135,6 +135,9 @@ if [[ ! -f /requirements.installed ]]; then # Remove packages that were only required for build. apk del $APK_FLAGS $BUILD_PACKAGES "${BUILD_REQUIREMENTS[@]}" + vlog "Installing APK requirements..." + apk add $APK_FLAGS "${APK_REQUIREMENTS[@]}" + touch /requirements.installed else vlog "/requirements.installed file exists-- skipping requirements installs." diff --git a/2.7-slim/Dockerfile b/2.7-slim/Dockerfile index 19d96a3..d31f322 100644 --- a/2.7-slim/Dockerfile +++ b/2.7-slim/Dockerfile @@ -1,60 +1,110 @@ -FROM alpine:3.8 +FROM alpine:3.11 -ENV ALPINE_VERSION=3.8 +# VERSIONS +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=2.7.16 -# These are always installed. Notes: -# * dumb-init: a proper init system for containers, to reap zombie children -# * bash: For entrypoint, and debugging -# * ca-certificates: for SSL verification during Pip and easy_install -# * python: the binaries themselves -# * py-setuptools: required only in major version 2, installs easy_install so we can install Pip. -ENV PACKAGES="\ - dumb-init \ - bash \ - ca-certificates \ - python2 \ - py-setuptools \ -" - -RUN echo \ - # replacing default repositories with edge ones - && echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" > /etc/apk/repositories \ - && echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories \ - && echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories \ - # Add the packages, with a CDN-breakage fallback if needed - && apk add --no-cache $PACKAGES || \ - (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) \ - # turn back the clock -- so hacky! - && echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories \ - # && echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories \ - # && echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories \ - # && echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \ - # make some useful symlinks that are expected to exist - && if [[ ! -e /usr/bin/python ]]; then ln -sf /usr/bin/python2.7 /usr/bin/python; fi \ - && if [[ ! -e /usr/bin/python-config ]]; then ln -sf /usr/bin/python2.7-config /usr/bin/python-config; fi \ - && if [[ ! -e /usr/bin/easy_install ]]; then ln -sf /usr/bin/easy_install-2.7 /usr/bin/easy_install; fi \ - # Install and upgrade Pip - && easy_install pip \ - && pip install --upgrade pip \ - && if [[ ! -e /usr/bin/pip ]]; then ln -sf /usr/bin/pip2.7 /usr/bin/pip; fi \ - && echo +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. Notes: + # * dumb-init: a proper init system for containers, to reap zombie children + # * bash: For entrypoint, and debugging + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + bash \ + ca-certificates \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + git \ + " \ + # These packages are not installed immediately, but are added at runtime or ONBUILD to shrink the image as much as possible. Notes: + # * build-base: used so we include the basic development packages (gcc) + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * lib6-compat: compatibility libraries for glibc + # * git: to ease up clones of repos + BUILD_PACKAGES="\ + build-base \ + linux-headers \ + libc6-compat \ + git \ + " # Copy in the entrypoint script -- this installs prerequisites on container start. COPY entrypoint.sh /entrypoint.sh -# These packages are not installed immediately, but are added at runtime or ONBUILD to shrink the image as much as possible. Notes: -# * build-base: used so we include the basic development packages (gcc) -# * linux-headers: commonly needed, and an unusual package name from Alpine. -# * lib6-compat: compatibility libraries for glibc -# * python2-dev: are used for gevent e.g. -# * git: to ease up clones of repos -ENV BUILD_PACKAGES="\ - build-base \ - linux-headers \ - libc6-compat \ - python2-dev \ - git \ -" +RUN set -ex ;\ + # find MAJOR and MINOR python versions based on $PYTHON_VERSION + export PYTHON_MAJOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f3- | rev) ;\ + export PYTHON_MINOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f2- | rev) ;\ + # replacing default repositories with edge ones + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories ;\ + # Add the packages, with a CDN-breakage fallback if needed + apk add --no-cache $PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) ;\ + # Add packages just for the python build process with a CDN-breakage fallback if needed + apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES) ;\ + # turn back the clock -- so hacky! + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories ;\ + # echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ + # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ + # use pyenv to download and compile specific python version + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ + # delete files to to reduce container size + # tips taken from main python docker repo + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # remove build dependencies and any leftover apk cache + apk del --no-cache --purge .build-deps ;\ + rm -rf /var/cache/apk/* ;\ + # set proper permission to run entrypoint script + chmod a+x /entrypoint.sh # This script installs APK and Pip prerequisites on container start, or ONBUILD. Notes: # * Reads the -a flags and /apk-requirements.txt for install requests diff --git a/2.7-slim/entrypoint.sh b/2.7-slim/entrypoint.sh index 901491a..50c2ab1 100755 --- a/2.7-slim/entrypoint.sh +++ b/2.7-slim/entrypoint.sh @@ -12,7 +12,7 @@ VERBOSITY=1 TMP_REQFILE='/tmp/requirements.txt' function usage () { - echo <<"EOF" + cat <<"EOF" Usage: $0 [-a -b -p -A -B -P -r -q -x] [--] -a : APK requirement. Can be specified multiple times. -b : APK build requirement. These will be removed at the end to save space. @@ -135,6 +135,9 @@ if [[ ! -f /requirements.installed ]]; then # Remove packages that were only required for build. apk del $APK_FLAGS $BUILD_PACKAGES "${BUILD_REQUIREMENTS[@]}" + vlog "Installing APK requirements..." + apk add $APK_FLAGS "${APK_REQUIREMENTS[@]}" + touch /requirements.installed else vlog "/requirements.installed file exists-- skipping requirements installs." diff --git a/2.7/Dockerfile b/2.7/Dockerfile index e697512..01384e4 100644 --- a/2.7/Dockerfile +++ b/2.7/Dockerfile @@ -1,41 +1,61 @@ -FROM alpine:3.8 +FROM alpine:3.11 # VERSIONS -ENV ALPINE_VERSION=3.8 \ - PYTHON_VERSION=2.7.15 +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=2.7.16 # PATHS -ENV PYTHON_PATH=/usr/lib/python$PYTHON_VERSION \ - PATH="/usr/lib/python$PYTHON_VERSION/bin/:${PATH}" - -# PACKAGES -# * dumb-init: a proper init system for containers, to reap zombie children -# * musl: standard C library -# * lib6-compat: compatibility libraries for glibc -# * linux-headers: commonly needed, and an unusual package name from Alpine. -# * build-base: used so we include the basic development packages (gcc) -# * bash: so we can access /bin/bash -# * git: to ease up clones of repos -# * ca-certificates: for SSL verification during Pip and easy_install -ENV PACKAGES="\ - dumb-init \ - musl \ - libc6-compat \ - linux-headers \ - build-base \ - bash \ - git \ - ca-certificates \ -" - -# PACKAGES needed to built python -ENV PYTHON_BUILD_PACKAGES="\ - readline-dev \ - zlib-dev \ - bzip2-dev \ - sqlite-dev \ - openssl-dev \ -" +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. + # * dumb-init: a proper init system for containers, to reap zombie children + # * musl: standard C library + # * lib6-compat: compatibility libraries for glibc + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * build-base: used so we include the basic development packages (gcc) + # * bash: so we can access /bin/bash + # * git: to ease up clones of repos + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + musl \ + libc6-compat \ + linux-headers \ + build-base \ + bash \ + git \ + ca-certificates \ + libressl-dev \ + tzdata \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + git \ + " RUN set -ex ;\ # find MAJOR and MINOR python versions based on $PYTHON_VERSION @@ -56,26 +76,31 @@ RUN set -ex ;\ # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ # use pyenv to download and compile specific python version - git clone --depth 1 https://github.com/pyenv/pyenv /usr/lib/pyenv ;\ - PYENV_ROOT=/usr/lib/pyenv /usr/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ - # move specific version to correct path delete pyenv, no longer needed - mv /usr/lib/pyenv/versions/$PYTHON_VERSION/ $PYTHON_PATH ;\ - rm -rfv /usr/lib/pyenv ;\ - # change the path on the header of every file from PYENV_ROOT to PYTHON_PATH - cd $PYTHON_PATH/bin/ && sed -i "s+/usr/lib/pyenv/versions/$PYTHON_VERSION/+$PYTHON_PATH/+g" * ;\ - # delete binary "duplicates" and replace them with symlinks - # this also optimizes space since they are actually the same binary - rm -f $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION \ - $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION \ - $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION-config \ - $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION-config ;\ - ln -sf $PYTHON_PATH/bin/python $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION ;\ - ln -sf $PYTHON_PATH/bin/python $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION ;\ - ln -sf $PYTHON_PATH/bin/python-config $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION-config ;\ - ln -sf $PYTHON_PATH/bin/python-config $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION-config ;\ + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ # delete files to to reduce container size # tips taken from main python docker repo - find /usr/lib/python$PYTHON_VERSION -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # set timezone info + ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime ;\ # remove build dependencies and any leftover apk cache apk del --no-cache --purge .build-deps ;\ rm -rf /var/cache/apk/* diff --git a/3.6-onbuild/Dockerfile b/3.6-onbuild/Dockerfile index 43e37d3..08f8787 100644 --- a/3.6-onbuild/Dockerfile +++ b/3.6-onbuild/Dockerfile @@ -1,41 +1,64 @@ -FROM alpine:3.8 +FROM alpine:3.11 # VERSIONS -ENV ALPINE_VERSION=3.8 \ - PYTHON_VERSION=3.6.6 +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=3.6.9 # PATHS -ENV PYTHON_PATH=/usr/lib/python$PYTHON_VERSION \ - PATH="/usr/lib/python$PYTHON_VERSION/bin/:${PATH}" +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. + # * dumb-init: a proper init system for containers, to reap zombie children + # * musl: standard C library + # * lib6-compat: compatibility libraries for glibc + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * build-base: used so we include the basic development packages (gcc) + # * bash: so we can access /bin/bash + # * git: to ease up clones of repos + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + musl \ + libc6-compat \ + linux-headers \ + build-base \ + bash \ + git \ + ca-certificates \ + libressl-dev \ + tzdata \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + git \ + " -# PACKAGES -# * dumb-init: a proper init system for containers, to reap zombie children -# * musl: standard C library -# * lib6-compat: compatibility libraries for glibc -# * linux-headers: commonly needed, and an unusual package name from Alpine. -# * build-base: used so we include the basic development packages (gcc) -# * bash: so we can access /bin/bash -# * git: to ease up clones of repos -# * ca-certificates: for SSL verification during Pip and easy_install -ENV PACKAGES="\ - dumb-init \ - musl \ - libc6-compat \ - linux-headers \ - build-base \ - bash \ - git \ - ca-certificates \ -" - -# PACKAGES needed to built python -ENV PYTHON_BUILD_PACKAGES="\ - readline-dev \ - zlib-dev \ - bzip2-dev \ - sqlite-dev \ - openssl-dev \ -" +# Copy in the entrypoint script -- this installs prerequisites on container start. +COPY entrypoint.sh /entrypoint.sh RUN set -ex ;\ # find MAJOR and MINOR python versions based on $PYTHON_VERSION @@ -56,32 +79,36 @@ RUN set -ex ;\ # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ # use pyenv to download and compile specific python version - git clone --depth 1 https://github.com/pyenv/pyenv /usr/lib/pyenv ;\ - PYENV_ROOT=/usr/lib/pyenv /usr/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ - # move specific version to correct path delete pyenv, no longer needed - mv /usr/lib/pyenv/versions/$PYTHON_VERSION/ $PYTHON_PATH ;\ - rm -rfv /usr/lib/pyenv ;\ - # change the path on the header of every file from PYENV_ROOT to PYTHON_PATH - cd $PYTHON_PATH/bin/ && sed -i "s+/usr/lib/pyenv/versions/$PYTHON_VERSION/+$PYTHON_PATH/+g" * ;\ - # delete binary "duplicates" and replace them with symlinks - # this also optimizes space since they are actually the same binary - rm -f $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION \ - $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION \ - $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION-config \ - $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION-config ;\ - ln -sf $PYTHON_PATH/bin/python $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION ;\ - ln -sf $PYTHON_PATH/bin/python $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION ;\ - ln -sf $PYTHON_PATH/bin/python-config $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION-config ;\ - ln -sf $PYTHON_PATH/bin/python-config $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION-config ;\ + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ # delete files to to reduce container size # tips taken from main python docker repo - find /usr/lib/python$PYTHON_VERSION -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # set timezone info + ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime ;\ # remove build dependencies and any leftover apk cache apk del --no-cache --purge .build-deps ;\ - rm -rf /var/cache/apk/* - -# Copy in the entrypoint script -- this installs prerequisites on container start. -COPY entrypoint.sh /entrypoint.sh + rm -rf /var/cache/apk/* ;\ + # set proper permission to run entrypoint script + chmod a+x /entrypoint.sh # install requirements # this way when you build you won't need to install again diff --git a/3.6-onbuild/entrypoint.sh b/3.6-onbuild/entrypoint.sh index 901491a..50c2ab1 100755 --- a/3.6-onbuild/entrypoint.sh +++ b/3.6-onbuild/entrypoint.sh @@ -12,7 +12,7 @@ VERBOSITY=1 TMP_REQFILE='/tmp/requirements.txt' function usage () { - echo <<"EOF" + cat <<"EOF" Usage: $0 [-a -b -p -A -B -P -r -q -x] [--] -a : APK requirement. Can be specified multiple times. -b : APK build requirement. These will be removed at the end to save space. @@ -135,6 +135,9 @@ if [[ ! -f /requirements.installed ]]; then # Remove packages that were only required for build. apk del $APK_FLAGS $BUILD_PACKAGES "${BUILD_REQUIREMENTS[@]}" + vlog "Installing APK requirements..." + apk add $APK_FLAGS "${APK_REQUIREMENTS[@]}" + touch /requirements.installed else vlog "/requirements.installed file exists-- skipping requirements installs." diff --git a/3.6-slim/Dockerfile b/3.6-slim/Dockerfile index c0ef390..976838f 100644 --- a/3.6-slim/Dockerfile +++ b/3.6-slim/Dockerfile @@ -1,60 +1,111 @@ -FROM alpine:3.8 +FROM alpine:3.11 -ENV ALPINE_VERSION=3.8 +# VERSIONS +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=3.6.9 -# These are always installed. Notes: -# * dumb-init: a proper init system for containers, to reap zombie children -# * bash: For entrypoint, and debugging -# * ca-certificates: for SSL verification during Pip and easy_install -# * python: the binaries themselves -ENV PACKAGES="\ - dumb-init \ - bash \ - ca-certificates \ - python3 \ -" - -RUN echo \ - # replacing default repositories with edge ones - && echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" > /etc/apk/repositories \ - && echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories \ - && echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories \ - # Add the packages, with a CDN-breakage fallback if needed - && apk add --no-cache $PACKAGES || \ - (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) \ - # turn back the clock -- so hacky! - && echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories \ - # && echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories \ - # && echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories \ - # && echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \ - # make some useful symlinks that are expected to exist - && if [[ ! -e /usr/bin/python ]]; then ln -sf /usr/bin/python3 /usr/bin/python; fi \ - && if [[ ! -e /usr/bin/python-config ]]; then ln -sf /usr/bin/python-config3 /usr/bin/python-config; fi \ - && if [[ ! -e /usr/bin/idle ]]; then ln -sf /usr/bin/idle3 /usr/bin/idle; fi \ - && if [[ ! -e /usr/bin/pydoc ]]; then ln -sf /usr/bin/pydoc3 /usr/bin/pydoc; fi \ - && if [[ ! -e /usr/bin/easy_install ]]; then ln -sf $(ls /usr/bin/easy_install*) /usr/bin/easy_install; fi \ - # Install and upgrade Pip - && easy_install pip \ - && pip install --upgrade pip \ - && if [[ ! -e /usr/bin/pip ]]; then ln -sf /usr/bin/pip3 /usr/bin/pip; fi \ - && echo +# PATHS +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. Notes: + # * dumb-init: a proper init system for containers, to reap zombie children + # * bash: For entrypoint, and debugging + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + bash \ + ca-certificates \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + git \ + " \ + # These packages are not installed immediately, but are added at runtime or ONBUILD to shrink the image as much as possible. Notes: + # * build-base: used so we include the basic development packages (gcc) + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * lib6-compat: compatibility libraries for glibc + # * git: to ease up clones of repos + BUILD_PACKAGES="\ + build-base \ + linux-headers \ + libc6-compat \ + git \ + " # Copy in the entrypoint script -- this installs prerequisites on container start. COPY entrypoint.sh /entrypoint.sh -# These packages are not installed immediately, but are added at runtime or ONBUILD to shrink the image as much as possible. Notes: -# * build-base: used so we include the basic development packages (gcc) -# * linux-headers: commonly needed, and an unusual package name from Alpine. -# * lib6-compat: compatibility libraries for glibc -# * python3-dev: are used for gevent e.g. -# * git: to ease up clones of repos -ENV BUILD_PACKAGES="\ - build-base \ - linux-headers \ - libc6-compat \ - python3-dev \ - git \ -" +RUN set -ex ;\ + # find MAJOR and MINOR python versions based on $PYTHON_VERSION + export PYTHON_MAJOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f3- | rev) ;\ + export PYTHON_MINOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f2- | rev) ;\ + # replacing default repositories with edge ones + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories ;\ + # Add the packages, with a CDN-breakage fallback if needed + apk add --no-cache $PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) ;\ + # Add packages just for the python build process with a CDN-breakage fallback if needed + apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES) ;\ + # turn back the clock -- so hacky! + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories ;\ + # echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ + # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ + # use pyenv to download and compile specific python version + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ + # delete files to to reduce container size + # tips taken from main python docker repo + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # remove build dependencies and any leftover apk cache + apk del --no-cache --purge .build-deps ;\ + rm -rf /var/cache/apk/* ;\ + # set proper permission to run entrypoint script + chmod a+x /entrypoint.sh # This script installs APK and Pip prerequisites on container start, or ONBUILD. Notes: # * Reads the -a flags and /apk-requirements.txt for install requests diff --git a/3.6-slim/entrypoint.sh b/3.6-slim/entrypoint.sh index 901491a..50c2ab1 100755 --- a/3.6-slim/entrypoint.sh +++ b/3.6-slim/entrypoint.sh @@ -12,7 +12,7 @@ VERBOSITY=1 TMP_REQFILE='/tmp/requirements.txt' function usage () { - echo <<"EOF" + cat <<"EOF" Usage: $0 [-a -b -p -A -B -P -r -q -x] [--] -a : APK requirement. Can be specified multiple times. -b : APK build requirement. These will be removed at the end to save space. @@ -135,6 +135,9 @@ if [[ ! -f /requirements.installed ]]; then # Remove packages that were only required for build. apk del $APK_FLAGS $BUILD_PACKAGES "${BUILD_REQUIREMENTS[@]}" + vlog "Installing APK requirements..." + apk add $APK_FLAGS "${APK_REQUIREMENTS[@]}" + touch /requirements.installed else vlog "/requirements.installed file exists-- skipping requirements installs." diff --git a/3.6/Dockerfile b/3.6/Dockerfile index f2a6b7c..ecfbfd8 100644 --- a/3.6/Dockerfile +++ b/3.6/Dockerfile @@ -1,41 +1,61 @@ -FROM alpine:3.8 +FROM alpine:3.11 # VERSIONS -ENV ALPINE_VERSION=3.8 \ - PYTHON_VERSION=3.6.6 +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=3.6.9 # PATHS -ENV PYTHON_PATH=/usr/lib/python$PYTHON_VERSION \ - PATH="/usr/lib/python$PYTHON_VERSION/bin/:${PATH}" - -# PACKAGES -# * dumb-init: a proper init system for containers, to reap zombie children -# * musl: standard C library -# * lib6-compat: compatibility libraries for glibc -# * linux-headers: commonly needed, and an unusual package name from Alpine. -# * build-base: used so we include the basic development packages (gcc) -# * bash: so we can access /bin/bash -# * git: to ease up clones of repos -# * ca-certificates: for SSL verification during Pip and easy_install -ENV PACKAGES="\ - dumb-init \ - musl \ - libc6-compat \ - linux-headers \ - build-base \ - bash \ - git \ - ca-certificates \ -" - -# PACKAGES needed to built python -ENV PYTHON_BUILD_PACKAGES="\ - readline-dev \ - zlib-dev \ - bzip2-dev \ - sqlite-dev \ - openssl-dev \ -" +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. + # * dumb-init: a proper init system for containers, to reap zombie children + # * musl: standard C library + # * lib6-compat: compatibility libraries for glibc + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * build-base: used so we include the basic development packages (gcc) + # * bash: so we can access /bin/bash + # * git: to ease up clones of repos + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + musl \ + libc6-compat \ + linux-headers \ + build-base \ + bash \ + git \ + ca-certificates \ + libressl-dev \ + tzdata \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + git \ + " RUN set -ex ;\ # find MAJOR and MINOR python versions based on $PYTHON_VERSION @@ -56,26 +76,31 @@ RUN set -ex ;\ # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ # use pyenv to download and compile specific python version - git clone --depth 1 https://github.com/pyenv/pyenv /usr/lib/pyenv ;\ - PYENV_ROOT=/usr/lib/pyenv /usr/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ - # move specific version to correct path delete pyenv, no longer needed - mv /usr/lib/pyenv/versions/$PYTHON_VERSION/ $PYTHON_PATH ;\ - rm -rfv /usr/lib/pyenv ;\ - # change the path on the header of every file from PYENV_ROOT to PYTHON_PATH - cd $PYTHON_PATH/bin/ && sed -i "s+/usr/lib/pyenv/versions/$PYTHON_VERSION/+$PYTHON_PATH/+g" * ;\ - # delete binary "duplicates" and replace them with symlinks - # this also optimizes space since they are actually the same binary - rm -f $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION \ - $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION \ - $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION-config \ - $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION-config ;\ - ln -sf $PYTHON_PATH/bin/python $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION ;\ - ln -sf $PYTHON_PATH/bin/python $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION ;\ - ln -sf $PYTHON_PATH/bin/python-config $PYTHON_PATH/bin/python$PYTHON_MAJOR_VERSION-config ;\ - ln -sf $PYTHON_PATH/bin/python-config $PYTHON_PATH/bin/python$PYTHON_MINOR_VERSION-config ;\ + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ # delete files to to reduce container size # tips taken from main python docker repo - find /usr/lib/python$PYTHON_VERSION -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # set timezone info + ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime ;\ # remove build dependencies and any leftover apk cache apk del --no-cache --purge .build-deps ;\ rm -rf /var/cache/apk/* diff --git a/3.7-onbuild/Dockerfile b/3.7-onbuild/Dockerfile new file mode 100644 index 0000000..1303fd1 --- /dev/null +++ b/3.7-onbuild/Dockerfile @@ -0,0 +1,124 @@ +FROM alpine:3.11 + +# VERSIONS +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=3.7.3 + +# PATHS +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. + # * dumb-init: a proper init system for containers, to reap zombie children + # * musl: standard C library + # * lib6-compat: compatibility libraries for glibc + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * build-base: used so we include the basic development packages (gcc) + # * bash: so we can access /bin/bash + # * git: to ease up clones of repos + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + musl \ + libc6-compat \ + linux-headers \ + build-base \ + bash \ + git \ + ca-certificates \ + libressl-dev \ + tzdata \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + git \ + " + +# Copy in the entrypoint script -- this installs prerequisites on container start. +COPY entrypoint.sh /entrypoint.sh + +RUN set -ex ;\ + # find MAJOR and MINOR python versions based on $PYTHON_VERSION + export PYTHON_MAJOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f3- | rev) ;\ + export PYTHON_MINOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f2- | rev) ;\ + # replacing default repositories with edge ones + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories ;\ + # Add the packages, with a CDN-breakage fallback if needed + apk add --no-cache $PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) ;\ + # Add packages just for the python build process with a CDN-breakage fallback if needed + apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES) ;\ + # turn back the clock -- so hacky! + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories ;\ + # echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ + # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ + # use pyenv to download and compile specific python version + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ + # delete files to to reduce container size + # tips taken from main python docker repo + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # set timezone info + ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime ;\ + # remove build dependencies and any leftover apk cache + apk del --no-cache --purge .build-deps ;\ + rm -rf /var/cache/apk/* ;\ + # set proper permission to run entrypoint script + chmod a+x /entrypoint.sh + +# install requirements +# this way when you build you won't need to install again +# and since COPY is cached we don't need to wait +ONBUILD COPY requirements.txt /tmp/requirements.txt + +# Run the dependencies installer and then allow it to be run again if needed. +ONBUILD RUN /entrypoint.sh -r /tmp/requirements.txt +ONBUILD RUN rm -f /requirements.installed + +# since we will be "always" mounting the volume, we can set this up +ENTRYPOINT ["/usr/bin/dumb-init"] +CMD ["python"] diff --git a/3.7-onbuild/entrypoint.sh b/3.7-onbuild/entrypoint.sh new file mode 100755 index 0000000..50c2ab1 --- /dev/null +++ b/3.7-onbuild/entrypoint.sh @@ -0,0 +1,153 @@ +#!/usr/bin/dumb-init /bin/bash +set -e + +APK_REQUIREMENTS=() +BUILD_REQUIREMENTS=() +PIP_REQUIREMENTS=() +APKFILE='/apk-requirements.txt' +BUILDFILE='/build-requirements.txt' +REQFILE='/requirements.txt' +VERBOSITY=1 + +TMP_REQFILE='/tmp/requirements.txt' + +function usage () { + cat <<"EOF" +Usage: $0 [-a -b -p -A -B -P -r -q -x] [--] + -a : APK requirement. Can be specified multiple times. + -b : APK build requirement. These will be removed at the end to save space. + -p : Pip requirement. Can be specified multiple times. + + -A : apk-requirements.txt file location, default: /apk-requirements.txt + -B : build-requirements.txt file location, default: /build-requirements.txt + -P : requirements.txt file location, default: /requirements.txt + -r : same as above, just to match Pip's -r flag. + + -q : quiet, doesn't print anything at all. + -x : Bash debug mode. Extremely verbose! + + -- : Separator for flags and your command + + Whatever you provide after your arguments is run at the end. +EOF + exit 1 +} + +function vlog () { + if [ $VERBOSITY -gt 0 ]; then + echo $1 + fi +} + +# Get and process arguments +while getopts ":a:b:p:A:B:P:r:qx" opt; do + case $opt in + a) APK_REQUIREMENTS+=("$OPTARG") ;; + b) BUILD_REQUIREMENTS+=("$OPTARG") ;; + p) PIP_REQUIREMENTS+=("$OPTARG") ;; + A) APKFILE="$OPTARG" ;; + B) BUILDFILE="$OPTARG" ;; + P) REQFILE="$OPTARG" ;; + r) REQFILE="$OPTARG" ;; + q) VERBOSITY=0 ;; + x) set -x ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + usage + ;; + esac +done + +# Bad arguments +if [ $? -ne 0 ]; +then + usage +fi + +# Strip out all the arguments that have been processed +shift $((OPTIND-1)) + +# If there's a double dash at the end, get that off +[[ $1 = "--" ]] && shift + +# Make some common flags objects +PIP_FLAGS='' +if [ $VERBOSITY -eq 0 ]; then + PIP_FLAGS="$PIP_FLAGS -q" +fi + +APK_FLAGS='--no-cache --no-progress' +if [ $VERBOSITY -eq 0 ]; then + APK_FLAGS="$APK_FLAGS -q" +fi + +# Don't do anything if we've already done this. +if [[ ! -f /requirements.installed ]]; then + vlog "First run, checking for any requirements..." + + # Install any APK requirements + if [[ -f "$APKFILE" ]]; then + vlog "APK requirements file detected!" + APK_REQUIREMENTS+=($( cat "$APKFILE" )) + fi + + if [[ -f "$BUILDFILE" ]]; then + vlog "Build requirements file detected!" + BUILD_REQUIREMENTS+=($( cat "$BUILDFILE" )) + fi + + # Unfortunately the Alpine repositories are in a slightly inconsistent state for now-- python2 only exists in 'edge', not main. + # if [[ "$PYTHON_VERSION" == '2' ]]; then BUILD_PACKAGES="$(echo $BUILD_PACKAGES | sed -e 's/python2/python/g')"; fi \ + vlog "Installing all APK requirements..." + apk add $APK_FLAGS $BUILD_PACKAGES "${APK_REQUIREMENTS[@]}" "${BUILD_REQUIREMENTS[@]}" + + # Install any Pip requirements + if [[ -f "$REQFILE" && "$(cat $REQFILE | wc -l)" -gt 0 ]]; then + # Do this check a little early-- since we merge cli in with file, + # we'd get a false positive for logging otherwise. + vlog "Pip requirements file detected!" + fi + + # If we use CLI parameters, we'll have to reassign this. + TARGET_REQFILE="$REQFILE" + if [[ ${#PIP_REQUIREMENTS[@]} -gt 0 ]]; then + # Put all Pip requirements into the same file. + printf "%s\n" "${PIP_REQUIREMENTS[@]}" >> "$TMP_REQFILE" + + if [[ -f "$REQFILE" && "$(cat $REQFILE | wc -l)" -gt 0 ]]; then + cat "$REQFILE" >> "$TMP_REQFILE" + fi + + TARGET_REQFILE="$TMP_REQFILE" + fi + + if [[ -f $TARGET_REQFILE && "$(cat $TARGET_REQFILE | wc -l)" -gt 0 ]]; then + vlog "Upgrading Pip..." + pip install $PIP_FLAGS --upgrade pip + vlog "Installing all Pip requirements..." + pip install $PIP_FLAGS -r "$TARGET_REQFILE" + fi + + # Remove packages that were only required for build. + apk del $APK_FLAGS $BUILD_PACKAGES "${BUILD_REQUIREMENTS[@]}" + + vlog "Installing APK requirements..." + apk add $APK_FLAGS "${APK_REQUIREMENTS[@]}" + + touch /requirements.installed +else + vlog "/requirements.installed file exists-- skipping requirements installs." +fi + + +if [[ ! -z "$@" ]]; then + # If the user has given us a command, run it. + $@ +else + # Otherwise, default to running 'python'. + python +fi diff --git a/3.7-slim/Dockerfile b/3.7-slim/Dockerfile new file mode 100644 index 0000000..d6b7888 --- /dev/null +++ b/3.7-slim/Dockerfile @@ -0,0 +1,115 @@ +FROM alpine:3.11 + +# VERSIONS +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=3.7.3 + +# PATHS +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. Notes: + # * dumb-init: a proper init system for containers, to reap zombie children + # * bash: For entrypoint, and debugging + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + bash \ + ca-certificates \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + git \ + " \ + # These packages are not installed immediately, but are added at runtime or ONBUILD to shrink the image as much as possible. Notes: + # * build-base: used so we include the basic development packages (gcc) + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * lib6-compat: compatibility libraries for glibc + # * git: to ease up clones of repos + BUILD_PACKAGES="\ + build-base \ + linux-headers \ + libc6-compat \ + git \ + " + +# Copy in the entrypoint script -- this installs prerequisites on container start. +COPY entrypoint.sh /entrypoint.sh + +RUN set -ex ;\ + # find MAJOR and MINOR python versions based on $PYTHON_VERSION + export PYTHON_MAJOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f3- | rev) ;\ + export PYTHON_MINOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f2- | rev) ;\ + # replacing default repositories with edge ones + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories ;\ + # Add the packages, with a CDN-breakage fallback if needed + apk add --no-cache $PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) ;\ + # Add packages just for the python build process with a CDN-breakage fallback if needed + apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES) ;\ + # turn back the clock -- so hacky! + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories ;\ + # echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ + # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ + # use pyenv to download and compile specific python version + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ + # delete files to to reduce container size + # tips taken from main python docker repo + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # remove build dependencies and any leftover apk cache + apk del --no-cache --purge .build-deps ;\ + rm -rf /var/cache/apk/* ;\ + # set proper permission to run entrypoint script + chmod a+x /entrypoint.sh + +# This script installs APK and Pip prerequisites on container start, or ONBUILD. Notes: +# * Reads the -a flags and /apk-requirements.txt for install requests +# * Reads the -b flags and /build-requirements.txt for build packages -- removed when build is complete +# * Reads the -p flags and /requirements.txt for Pip packages +# * Reads the -r flag to specify a different file path for /requirements.txt +ENTRYPOINT ["/usr/bin/dumb-init", "bash", "/entrypoint.sh"] diff --git a/3.7-slim/entrypoint.sh b/3.7-slim/entrypoint.sh new file mode 100644 index 0000000..50c2ab1 --- /dev/null +++ b/3.7-slim/entrypoint.sh @@ -0,0 +1,153 @@ +#!/usr/bin/dumb-init /bin/bash +set -e + +APK_REQUIREMENTS=() +BUILD_REQUIREMENTS=() +PIP_REQUIREMENTS=() +APKFILE='/apk-requirements.txt' +BUILDFILE='/build-requirements.txt' +REQFILE='/requirements.txt' +VERBOSITY=1 + +TMP_REQFILE='/tmp/requirements.txt' + +function usage () { + cat <<"EOF" +Usage: $0 [-a -b -p -A -B -P -r -q -x] [--] + -a : APK requirement. Can be specified multiple times. + -b : APK build requirement. These will be removed at the end to save space. + -p : Pip requirement. Can be specified multiple times. + + -A : apk-requirements.txt file location, default: /apk-requirements.txt + -B : build-requirements.txt file location, default: /build-requirements.txt + -P : requirements.txt file location, default: /requirements.txt + -r : same as above, just to match Pip's -r flag. + + -q : quiet, doesn't print anything at all. + -x : Bash debug mode. Extremely verbose! + + -- : Separator for flags and your command + + Whatever you provide after your arguments is run at the end. +EOF + exit 1 +} + +function vlog () { + if [ $VERBOSITY -gt 0 ]; then + echo $1 + fi +} + +# Get and process arguments +while getopts ":a:b:p:A:B:P:r:qx" opt; do + case $opt in + a) APK_REQUIREMENTS+=("$OPTARG") ;; + b) BUILD_REQUIREMENTS+=("$OPTARG") ;; + p) PIP_REQUIREMENTS+=("$OPTARG") ;; + A) APKFILE="$OPTARG" ;; + B) BUILDFILE="$OPTARG" ;; + P) REQFILE="$OPTARG" ;; + r) REQFILE="$OPTARG" ;; + q) VERBOSITY=0 ;; + x) set -x ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + usage + ;; + esac +done + +# Bad arguments +if [ $? -ne 0 ]; +then + usage +fi + +# Strip out all the arguments that have been processed +shift $((OPTIND-1)) + +# If there's a double dash at the end, get that off +[[ $1 = "--" ]] && shift + +# Make some common flags objects +PIP_FLAGS='' +if [ $VERBOSITY -eq 0 ]; then + PIP_FLAGS="$PIP_FLAGS -q" +fi + +APK_FLAGS='--no-cache --no-progress' +if [ $VERBOSITY -eq 0 ]; then + APK_FLAGS="$APK_FLAGS -q" +fi + +# Don't do anything if we've already done this. +if [[ ! -f /requirements.installed ]]; then + vlog "First run, checking for any requirements..." + + # Install any APK requirements + if [[ -f "$APKFILE" ]]; then + vlog "APK requirements file detected!" + APK_REQUIREMENTS+=($( cat "$APKFILE" )) + fi + + if [[ -f "$BUILDFILE" ]]; then + vlog "Build requirements file detected!" + BUILD_REQUIREMENTS+=($( cat "$BUILDFILE" )) + fi + + # Unfortunately the Alpine repositories are in a slightly inconsistent state for now-- python2 only exists in 'edge', not main. + # if [[ "$PYTHON_VERSION" == '2' ]]; then BUILD_PACKAGES="$(echo $BUILD_PACKAGES | sed -e 's/python2/python/g')"; fi \ + vlog "Installing all APK requirements..." + apk add $APK_FLAGS $BUILD_PACKAGES "${APK_REQUIREMENTS[@]}" "${BUILD_REQUIREMENTS[@]}" + + # Install any Pip requirements + if [[ -f "$REQFILE" && "$(cat $REQFILE | wc -l)" -gt 0 ]]; then + # Do this check a little early-- since we merge cli in with file, + # we'd get a false positive for logging otherwise. + vlog "Pip requirements file detected!" + fi + + # If we use CLI parameters, we'll have to reassign this. + TARGET_REQFILE="$REQFILE" + if [[ ${#PIP_REQUIREMENTS[@]} -gt 0 ]]; then + # Put all Pip requirements into the same file. + printf "%s\n" "${PIP_REQUIREMENTS[@]}" >> "$TMP_REQFILE" + + if [[ -f "$REQFILE" && "$(cat $REQFILE | wc -l)" -gt 0 ]]; then + cat "$REQFILE" >> "$TMP_REQFILE" + fi + + TARGET_REQFILE="$TMP_REQFILE" + fi + + if [[ -f $TARGET_REQFILE && "$(cat $TARGET_REQFILE | wc -l)" -gt 0 ]]; then + vlog "Upgrading Pip..." + pip install $PIP_FLAGS --upgrade pip + vlog "Installing all Pip requirements..." + pip install $PIP_FLAGS -r "$TARGET_REQFILE" + fi + + # Remove packages that were only required for build. + apk del $APK_FLAGS $BUILD_PACKAGES "${BUILD_REQUIREMENTS[@]}" + + vlog "Installing APK requirements..." + apk add $APK_FLAGS "${APK_REQUIREMENTS[@]}" + + touch /requirements.installed +else + vlog "/requirements.installed file exists-- skipping requirements installs." +fi + + +if [[ ! -z "$@" ]]; then + # If the user has given us a command, run it. + $@ +else + # Otherwise, default to running 'python'. + python +fi diff --git a/3.7/Dockerfile b/3.7/Dockerfile new file mode 100644 index 0000000..4c5c2ac --- /dev/null +++ b/3.7/Dockerfile @@ -0,0 +1,113 @@ +FROM alpine:3.11 + +# VERSIONS +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=3.7.3 + +# PATHS +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. + # * dumb-init: a proper init system for containers, to reap zombie children + # * musl: standard C library + # * lib6-compat: compatibility libraries for glibc + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * build-base: used so we include the basic development packages (gcc) + # * bash: so we can access /bin/bash + # * git: to ease up clones of repos + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + musl \ + libc6-compat \ + linux-headers \ + build-base \ + bash \ + git \ + ca-certificates \ + libressl-dev \ + libffi-dev \ + tzdata \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + git \ + " + +RUN set -ex ;\ + # find MAJOR and MINOR python versions based on $PYTHON_VERSION + export PYTHON_MAJOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f3- | rev) ;\ + export PYTHON_MINOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f2- | rev) ;\ + # replacing default repositories with edge ones + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories ;\ + # Add the packages, with a CDN-breakage fallback if needed + apk add --no-cache $PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) ;\ + # Add packages just for the python build process with a CDN-breakage fallback if needed + apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES) ;\ + # turn back the clock -- so hacky! + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories ;\ + # echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ + # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ + # use pyenv to download and compile specific python version + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + # flag explanation: + # --with-shared : python-dev + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ + # delete files to to reduce container size + # tips taken from main python docker repo + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # set timezone info + ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime ;\ + # remove build dependencies and any leftover apk cache + apk del --no-cache --purge .build-deps ;\ + rm -rf /var/cache/apk/* + +# since we will be "always" mounting the volume, we can set this up +ENTRYPOINT ["/usr/bin/dumb-init"] +CMD ["python"] diff --git a/3.8-onbuild/Dockerfile b/3.8-onbuild/Dockerfile new file mode 100644 index 0000000..dc13051 --- /dev/null +++ b/3.8-onbuild/Dockerfile @@ -0,0 +1,124 @@ +FROM alpine:3.11 + +# VERSIONS +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=3.8.2 + +# PATHS +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. + # * dumb-init: a proper init system for containers, to reap zombie children + # * musl: standard C library + # * lib6-compat: compatibility libraries for glibc + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * build-base: used so we include the basic development packages (gcc) + # * bash: so we can access /bin/bash + # * git: to ease up clones of repos + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + musl \ + libc6-compat \ + linux-headers \ + build-base \ + bash \ + git \ + ca-certificates \ + libressl-dev \ + tzdata \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg \ + dpkg-dev \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + " + +# Copy in the entrypoint script -- this installs prerequisites on container start. +COPY entrypoint.sh /entrypoint.sh + +RUN set -ex ;\ + # find MAJOR and MINOR python versions based on $PYTHON_VERSION + export PYTHON_MAJOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f3- | rev) ;\ + export PYTHON_MINOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f2- | rev) ;\ + # replacing default repositories with edge ones + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories ;\ + # Add the packages, with a CDN-breakage fallback if needed + apk add --no-cache $PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) ;\ + # Add packages just for the python build process with a CDN-breakage fallback if needed + apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES) ;\ + # turn back the clock -- so hacky! + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories ;\ + # echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ + # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ + # use pyenv to download and compile specific python version + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ + # delete files to to reduce container size + # tips taken from main python docker repo + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # set timezone info + ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime ;\ + # remove build dependencies and any leftover apk cache + apk del --no-cache --purge .build-deps ;\ + rm -rf /var/cache/apk/* ;\ + # set proper permission to run entrypoint script + chmod a+x /entrypoint.sh + +# install requirements +# this way when you build you won't need to install again +# and since COPY is cached we don't need to wait +ONBUILD COPY requirements.txt /tmp/requirements.txt + +# Run the dependencies installer and then allow it to be run again if needed. +ONBUILD RUN /entrypoint.sh -r /tmp/requirements.txt +ONBUILD RUN rm -f /requirements.installed + +# since we will be "always" mounting the volume, we can set this up +ENTRYPOINT ["/usr/bin/dumb-init"] +CMD ["python"] diff --git a/3.8-onbuild/entrypoint.sh b/3.8-onbuild/entrypoint.sh new file mode 100755 index 0000000..50c2ab1 --- /dev/null +++ b/3.8-onbuild/entrypoint.sh @@ -0,0 +1,153 @@ +#!/usr/bin/dumb-init /bin/bash +set -e + +APK_REQUIREMENTS=() +BUILD_REQUIREMENTS=() +PIP_REQUIREMENTS=() +APKFILE='/apk-requirements.txt' +BUILDFILE='/build-requirements.txt' +REQFILE='/requirements.txt' +VERBOSITY=1 + +TMP_REQFILE='/tmp/requirements.txt' + +function usage () { + cat <<"EOF" +Usage: $0 [-a -b -p -A -B -P -r -q -x] [--] + -a : APK requirement. Can be specified multiple times. + -b : APK build requirement. These will be removed at the end to save space. + -p : Pip requirement. Can be specified multiple times. + + -A : apk-requirements.txt file location, default: /apk-requirements.txt + -B : build-requirements.txt file location, default: /build-requirements.txt + -P : requirements.txt file location, default: /requirements.txt + -r : same as above, just to match Pip's -r flag. + + -q : quiet, doesn't print anything at all. + -x : Bash debug mode. Extremely verbose! + + -- : Separator for flags and your command + + Whatever you provide after your arguments is run at the end. +EOF + exit 1 +} + +function vlog () { + if [ $VERBOSITY -gt 0 ]; then + echo $1 + fi +} + +# Get and process arguments +while getopts ":a:b:p:A:B:P:r:qx" opt; do + case $opt in + a) APK_REQUIREMENTS+=("$OPTARG") ;; + b) BUILD_REQUIREMENTS+=("$OPTARG") ;; + p) PIP_REQUIREMENTS+=("$OPTARG") ;; + A) APKFILE="$OPTARG" ;; + B) BUILDFILE="$OPTARG" ;; + P) REQFILE="$OPTARG" ;; + r) REQFILE="$OPTARG" ;; + q) VERBOSITY=0 ;; + x) set -x ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + usage + ;; + esac +done + +# Bad arguments +if [ $? -ne 0 ]; +then + usage +fi + +# Strip out all the arguments that have been processed +shift $((OPTIND-1)) + +# If there's a double dash at the end, get that off +[[ $1 = "--" ]] && shift + +# Make some common flags objects +PIP_FLAGS='' +if [ $VERBOSITY -eq 0 ]; then + PIP_FLAGS="$PIP_FLAGS -q" +fi + +APK_FLAGS='--no-cache --no-progress' +if [ $VERBOSITY -eq 0 ]; then + APK_FLAGS="$APK_FLAGS -q" +fi + +# Don't do anything if we've already done this. +if [[ ! -f /requirements.installed ]]; then + vlog "First run, checking for any requirements..." + + # Install any APK requirements + if [[ -f "$APKFILE" ]]; then + vlog "APK requirements file detected!" + APK_REQUIREMENTS+=($( cat "$APKFILE" )) + fi + + if [[ -f "$BUILDFILE" ]]; then + vlog "Build requirements file detected!" + BUILD_REQUIREMENTS+=($( cat "$BUILDFILE" )) + fi + + # Unfortunately the Alpine repositories are in a slightly inconsistent state for now-- python2 only exists in 'edge', not main. + # if [[ "$PYTHON_VERSION" == '2' ]]; then BUILD_PACKAGES="$(echo $BUILD_PACKAGES | sed -e 's/python2/python/g')"; fi \ + vlog "Installing all APK requirements..." + apk add $APK_FLAGS $BUILD_PACKAGES "${APK_REQUIREMENTS[@]}" "${BUILD_REQUIREMENTS[@]}" + + # Install any Pip requirements + if [[ -f "$REQFILE" && "$(cat $REQFILE | wc -l)" -gt 0 ]]; then + # Do this check a little early-- since we merge cli in with file, + # we'd get a false positive for logging otherwise. + vlog "Pip requirements file detected!" + fi + + # If we use CLI parameters, we'll have to reassign this. + TARGET_REQFILE="$REQFILE" + if [[ ${#PIP_REQUIREMENTS[@]} -gt 0 ]]; then + # Put all Pip requirements into the same file. + printf "%s\n" "${PIP_REQUIREMENTS[@]}" >> "$TMP_REQFILE" + + if [[ -f "$REQFILE" && "$(cat $REQFILE | wc -l)" -gt 0 ]]; then + cat "$REQFILE" >> "$TMP_REQFILE" + fi + + TARGET_REQFILE="$TMP_REQFILE" + fi + + if [[ -f $TARGET_REQFILE && "$(cat $TARGET_REQFILE | wc -l)" -gt 0 ]]; then + vlog "Upgrading Pip..." + pip install $PIP_FLAGS --upgrade pip + vlog "Installing all Pip requirements..." + pip install $PIP_FLAGS -r "$TARGET_REQFILE" + fi + + # Remove packages that were only required for build. + apk del $APK_FLAGS $BUILD_PACKAGES "${BUILD_REQUIREMENTS[@]}" + + vlog "Installing APK requirements..." + apk add $APK_FLAGS "${APK_REQUIREMENTS[@]}" + + touch /requirements.installed +else + vlog "/requirements.installed file exists-- skipping requirements installs." +fi + + +if [[ ! -z "$@" ]]; then + # If the user has given us a command, run it. + $@ +else + # Otherwise, default to running 'python'. + python +fi diff --git a/3.8-slim/Dockerfile b/3.8-slim/Dockerfile new file mode 100644 index 0000000..06aa447 --- /dev/null +++ b/3.8-slim/Dockerfile @@ -0,0 +1,116 @@ +FROM alpine:3.11 + +# VERSIONS +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=3.8.2 + +# PATHS +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. Notes: + # * dumb-init: a proper init system for containers, to reap zombie children + # * bash: For entrypoint, and debugging + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + bash \ + ca-certificates \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg \ + dpkg-dev \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + git \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + " \ + # These packages are not installed immediately, but are added at runtime or ONBUILD to shrink the image as much as possible. Notes: + # * build-base: used so we include the basic development packages (gcc) + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * lib6-compat: compatibility libraries for glibc + # * git: to ease up clones of repos + BUILD_PACKAGES="\ + build-base \ + linux-headers \ + libc6-compat \ + git \ + " + +# Copy in the entrypoint script -- this installs prerequisites on container start. +COPY entrypoint.sh /entrypoint.sh + +RUN set -ex ;\ + # find MAJOR and MINOR python versions based on $PYTHON_VERSION + export PYTHON_MAJOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f3- | rev) ;\ + export PYTHON_MINOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f2- | rev) ;\ + # replacing default repositories with edge ones + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories ;\ + # Add the packages, with a CDN-breakage fallback if needed + apk add --no-cache $PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) ;\ + # Add packages just for the python build process with a CDN-breakage fallback if needed + apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES) ;\ + # turn back the clock -- so hacky! + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories ;\ + # echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ + # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ + # use pyenv to download and compile specific python version + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ + # delete files to to reduce container size + # tips taken from main python docker repo + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # remove build dependencies and any leftover apk cache + apk del --no-cache --purge .build-deps ;\ + rm -rf /var/cache/apk/* ;\ + # set proper permission to run entrypoint script + chmod a+x /entrypoint.sh + +# This script installs APK and Pip prerequisites on container start, or ONBUILD. Notes: +# * Reads the -a flags and /apk-requirements.txt for install requests +# * Reads the -b flags and /build-requirements.txt for build packages -- removed when build is complete +# * Reads the -p flags and /requirements.txt for Pip packages +# * Reads the -r flag to specify a different file path for /requirements.txt +ENTRYPOINT ["/usr/bin/dumb-init", "bash", "/entrypoint.sh"] diff --git a/3.8-slim/entrypoint.sh b/3.8-slim/entrypoint.sh new file mode 100644 index 0000000..50c2ab1 --- /dev/null +++ b/3.8-slim/entrypoint.sh @@ -0,0 +1,153 @@ +#!/usr/bin/dumb-init /bin/bash +set -e + +APK_REQUIREMENTS=() +BUILD_REQUIREMENTS=() +PIP_REQUIREMENTS=() +APKFILE='/apk-requirements.txt' +BUILDFILE='/build-requirements.txt' +REQFILE='/requirements.txt' +VERBOSITY=1 + +TMP_REQFILE='/tmp/requirements.txt' + +function usage () { + cat <<"EOF" +Usage: $0 [-a -b -p -A -B -P -r -q -x] [--] + -a : APK requirement. Can be specified multiple times. + -b : APK build requirement. These will be removed at the end to save space. + -p : Pip requirement. Can be specified multiple times. + + -A : apk-requirements.txt file location, default: /apk-requirements.txt + -B : build-requirements.txt file location, default: /build-requirements.txt + -P : requirements.txt file location, default: /requirements.txt + -r : same as above, just to match Pip's -r flag. + + -q : quiet, doesn't print anything at all. + -x : Bash debug mode. Extremely verbose! + + -- : Separator for flags and your command + + Whatever you provide after your arguments is run at the end. +EOF + exit 1 +} + +function vlog () { + if [ $VERBOSITY -gt 0 ]; then + echo $1 + fi +} + +# Get and process arguments +while getopts ":a:b:p:A:B:P:r:qx" opt; do + case $opt in + a) APK_REQUIREMENTS+=("$OPTARG") ;; + b) BUILD_REQUIREMENTS+=("$OPTARG") ;; + p) PIP_REQUIREMENTS+=("$OPTARG") ;; + A) APKFILE="$OPTARG" ;; + B) BUILDFILE="$OPTARG" ;; + P) REQFILE="$OPTARG" ;; + r) REQFILE="$OPTARG" ;; + q) VERBOSITY=0 ;; + x) set -x ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + usage + ;; + esac +done + +# Bad arguments +if [ $? -ne 0 ]; +then + usage +fi + +# Strip out all the arguments that have been processed +shift $((OPTIND-1)) + +# If there's a double dash at the end, get that off +[[ $1 = "--" ]] && shift + +# Make some common flags objects +PIP_FLAGS='' +if [ $VERBOSITY -eq 0 ]; then + PIP_FLAGS="$PIP_FLAGS -q" +fi + +APK_FLAGS='--no-cache --no-progress' +if [ $VERBOSITY -eq 0 ]; then + APK_FLAGS="$APK_FLAGS -q" +fi + +# Don't do anything if we've already done this. +if [[ ! -f /requirements.installed ]]; then + vlog "First run, checking for any requirements..." + + # Install any APK requirements + if [[ -f "$APKFILE" ]]; then + vlog "APK requirements file detected!" + APK_REQUIREMENTS+=($( cat "$APKFILE" )) + fi + + if [[ -f "$BUILDFILE" ]]; then + vlog "Build requirements file detected!" + BUILD_REQUIREMENTS+=($( cat "$BUILDFILE" )) + fi + + # Unfortunately the Alpine repositories are in a slightly inconsistent state for now-- python2 only exists in 'edge', not main. + # if [[ "$PYTHON_VERSION" == '2' ]]; then BUILD_PACKAGES="$(echo $BUILD_PACKAGES | sed -e 's/python2/python/g')"; fi \ + vlog "Installing all APK requirements..." + apk add $APK_FLAGS $BUILD_PACKAGES "${APK_REQUIREMENTS[@]}" "${BUILD_REQUIREMENTS[@]}" + + # Install any Pip requirements + if [[ -f "$REQFILE" && "$(cat $REQFILE | wc -l)" -gt 0 ]]; then + # Do this check a little early-- since we merge cli in with file, + # we'd get a false positive for logging otherwise. + vlog "Pip requirements file detected!" + fi + + # If we use CLI parameters, we'll have to reassign this. + TARGET_REQFILE="$REQFILE" + if [[ ${#PIP_REQUIREMENTS[@]} -gt 0 ]]; then + # Put all Pip requirements into the same file. + printf "%s\n" "${PIP_REQUIREMENTS[@]}" >> "$TMP_REQFILE" + + if [[ -f "$REQFILE" && "$(cat $REQFILE | wc -l)" -gt 0 ]]; then + cat "$REQFILE" >> "$TMP_REQFILE" + fi + + TARGET_REQFILE="$TMP_REQFILE" + fi + + if [[ -f $TARGET_REQFILE && "$(cat $TARGET_REQFILE | wc -l)" -gt 0 ]]; then + vlog "Upgrading Pip..." + pip install $PIP_FLAGS --upgrade pip + vlog "Installing all Pip requirements..." + pip install $PIP_FLAGS -r "$TARGET_REQFILE" + fi + + # Remove packages that were only required for build. + apk del $APK_FLAGS $BUILD_PACKAGES "${BUILD_REQUIREMENTS[@]}" + + vlog "Installing APK requirements..." + apk add $APK_FLAGS "${APK_REQUIREMENTS[@]}" + + touch /requirements.installed +else + vlog "/requirements.installed file exists-- skipping requirements installs." +fi + + +if [[ ! -z "$@" ]]; then + # If the user has given us a command, run it. + $@ +else + # Otherwise, default to running 'python'. + python +fi diff --git a/3.8/Dockerfile b/3.8/Dockerfile new file mode 100644 index 0000000..1c141c4 --- /dev/null +++ b/3.8/Dockerfile @@ -0,0 +1,113 @@ +FROM alpine:3.11 + +# VERSIONS +ENV ALPINE_VERSION=3.11 \ + PYTHON_VERSION=3.8.2 + +# PATHS +ENV PYTHON_PATH=/usr/local/bin/ \ + PATH="/usr/local/lib/python$PYTHON_VERSION/bin/:/usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin:${PATH}" \ + # These are always installed. + # * dumb-init: a proper init system for containers, to reap zombie children + # * musl: standard C library + # * lib6-compat: compatibility libraries for glibc + # * linux-headers: commonly needed, and an unusual package name from Alpine. + # * build-base: used so we include the basic development packages (gcc) + # * bash: so we can access /bin/bash + # * git: to ease up clones of repos + # * ca-certificates: for SSL verification during Pip and easy_install + PACKAGES="\ + dumb-init \ + musl \ + libc6-compat \ + linux-headers \ + build-base \ + bash \ + git \ + ca-certificates \ + libressl-dev \ + libffi-dev \ + tzdata \ + " \ + # PACKAGES needed to built python + PYTHON_BUILD_PACKAGES="\ + bzip2-dev \ + coreutils \ + dpkg \ + dpkg-dev \ + expat-dev \ + findutils \ + gcc \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + ncurses-dev \ + libressl-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + util-linux-dev \ + xz-dev \ + zlib-dev \ + " + +RUN set -ex ;\ + # find MAJOR and MINOR python versions based on $PYTHON_VERSION + export PYTHON_MAJOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f3- | rev) ;\ + export PYTHON_MINOR_VERSION=$(echo "${PYTHON_VERSION}" | rev | cut -d"." -f2- | rev) ;\ + # replacing default repositories with edge ones + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main" >> /etc/apk/repositories ;\ + # Add the packages, with a CDN-breakage fallback if needed + apk add --no-cache $PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache $PACKAGES) ;\ + # Add packages just for the python build process with a CDN-breakage fallback if needed + apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES || \ + (sed -i -e 's/dl-cdn/dl-4/g' /etc/apk/repositories && apk add --no-cache --virtual .build-deps $PYTHON_BUILD_PACKAGES) ;\ + # turn back the clock -- so hacky! + echo "http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/" > /etc/apk/repositories ;\ + # echo "@community http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/community" >> /etc/apk/repositories ;\ + # echo "@testing http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/testing" >> /etc/apk/repositories ;\ + # echo "@edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories ;\ + # use pyenv to download and compile specific python version + git clone --depth 1 https://github.com/pyenv/pyenv /usr/local/lib/pyenv ;\ + # install + GNU_ARCH="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" ;\ + # flag explanation: + # --with-shared : python-dev + PYENV_ROOT=/usr/local/lib/pyenv CONFIGURE_OPTS="--build=$GNU_ARCH --enable-loadable-sqlite-extensions --enable-shared --with-system-expat --with-system-ffi --without-ensurepip --with-shared" /usr/local/lib/pyenv/bin/pyenv install $PYTHON_VERSION ;\ + # keep the needed .so files + # ignore libpython - that one comes from the pyenv instalation + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | grep -ve 'libpython' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps ;\ + # for debug + # | xargs -n1 echo ;\ + # delete everything from pyenv except the installed version + # this throws an error but we ignore it + find /usr/local/lib/pyenv/ -mindepth 1 -name versions -prune -o -exec rm -rf {} \; || true ;\ + # delete files to to reduce container size + # tips taken from main python docker repo + find /usr/local/lib/pyenv/versions/$PYTHON_VERSION/ -depth \( -name '*.pyo' -o -name '*.pyc' -o -name 'test' -o -name 'tests' \) -exec rm -rf '{}' + ;\ + # symlink the binaries + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/bin/* $PYTHON_PATH ;\ + ln -s /usr/local/lib/pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_MINOR_VERSION /usr/include/ ;\ + # set timezone info + ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime ;\ + # remove build dependencies and any leftover apk cache + apk del --no-cache --purge .build-deps ;\ + rm -rf /var/cache/apk/* + +# since we will be "always" mounting the volume, we can set this up +ENTRYPOINT ["/usr/bin/dumb-init"] +CMD ["python"] diff --git a/README.md b/README.md index cf83f0f..1b362a8 100644 --- a/README.md +++ b/README.md @@ -11,20 +11,23 @@ A small Python Docker image based on [Alpine Linux](http://alpinelinux.org/). -- [Supported tags](#supported-tags) -- [Why?](#why) -- [Details](#details) -- [Usage](#usage) -- [Usage of onbuild images](#usage-of-onbuild-images) -- [Usage of slim images](#usage-of-slim-images) - - [Via `docker run`](#via-docker-run) - - [Pip Dependencies](#pip-dependencies) - - [Run-Time Dependencies](#run-time-dependencies) - - [Build-Time Dependencies](#build-time-dependencies) - - [Creating Images](#creating-images) - - [Debugging](#debugging) -- [License](#license) -- [TODO](#todo) +- [alpine-python](#alpine-python) + - [Supported tags](#supported-tags) + - [Why?](#why) + - [Details](#details) + - [Usage](#usage) + - [Usage of `onbuild` images](#usage-of-onbuild-images) + - [Usage of `slim` images](#usage-of-slim-images) + - [Via `docker run`](#via-docker-run) + - [Pip Dependencies](#pip-dependencies) + - [Run-Time Dependencies](#run-time-dependencies) + - [Build-Time Dependencies](#build-time-dependencies) + - [Creating Images](#creating-images) + - [Debugging](#debugging) + - [Additional Arguments](#additional-arguments) + - [Ecosystem](#ecosystem) + - [Contribution](#contribution) + - [License](#license) @@ -32,9 +35,15 @@ A small Python Docker image based on [Alpine Linux](http://alpinelinux.org/). * **`2.7` ([2.7/Dockerfile](https://github.com/jfloff/alpine-python/blob/master/2.7/Dockerfile))** * **`2.7-onbuild` ([2.7-onbuild/Dockerfile](https://github.com/jfloff/alpine-python/blob/master/2.7-onbuild/Dockerfile))** * **`2.7-slim` ([2.7-slim/Dockerfile](https://github.com/jfloff/alpine-python/blob/master/2.7-slim/Dockerfile))** -* **`3.6` `latest` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.6/Dockerfile))** +* **`3.6` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.6/Dockerfile))** * **`3.6-onbuild` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.6-onbuild/Dockerfile))** * **`3.6-slim` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.6-slim/Dockerfile))** +* **`3.7` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.7/Dockerfile))** +* **`3.7-onbuild` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.7-onbuild/Dockerfile))** +* **`3.7-slim` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.7-slim/Dockerfile))** +* **`3.8` `latest` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.8/Dockerfile))** +* **`3.8-onbuild` `latest-onbuild` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.8-onbuild/Dockerfile))** +* **`3.8-slim` `latest-slim` ([Dockerfile](https://github.com/jfloff/alpine-python/blob/master/3.8-slim/Dockerfile))** **NOTES:** - `onbuild` images install the `requirements.txt` of your project from the get go. This allows you to cache your requirements right in the build. _Make sure you are in the same directory of your `requirements.txt` file_. @@ -44,19 +53,26 @@ The default docker python images are too [big](https://github.com/docker-library |REPOSITORY|TAG|SIZE| |-|-|-| -|jfloff/alpine-python|2.7-slim|55MB| -|python|2.7-slim|138MB| +|jfloff/alpine-python|2.7-slim|60MB| +|python|2.7-slim|120MB| +|python|2.7-alpine|61.2MB| |||| -|jfloff/alpine-python|2.7|232MB| -|python|2.7-alpine|75.3MB| -|python|2.7|681MB| +|jfloff/alpine-python|2.7|235MB| +|python|2.7|912MB| |||| -|jfloff/alpine-python|3.6-slim|58.1MB| -|python|3.6-slim|156MB| +|jfloff/alpine-python|3.6-slim|76.3MB| +|python|3.6-slim|138MB| +|python|3.6-alpine|79MB| |||| -|jfloff/alpine-python|3.6|268MB| -|python|3.6|692MB| -|python|3.6-alpine|92.1MB| +|jfloff/alpine-python|3.6|252MB| +|python|3.6|922MB| +|||| +|jfloff/alpine-python|3.7-slim|80.4MB| +|python|3.7-slim|86.7MB| +|python|3.7-alpine|143MB| +|||| +|jfloff/alpine-python|3.7|256MB| +|python|3.7|927MB| Perhaps this could be even smaller, but I'm not an Alpine guru. **Feel free to post a PR.** @@ -210,17 +226,16 @@ docker run --rm -ti jfloff/alpine-python:2.7-slim bash These are some of the images that use `jfloff/alpine-python` as base image. *If you have another image that uses this as base image, please submit an issue or PR for it to be added. Image has to be published on Docker Hub.* - **[jfloff/alscipy](https://github.com/jfloff/docker-alscipy)** [![Docker Stars](https://img.shields.io/docker/stars/jfloff/alscipy.svg)][alscipy-hub] [![Docker Pulls](https://img.shields.io/docker/pulls/jfloff/alscipy.svg)][alscipy-hub] : image with common packages for Science in Alpine Python. - +- **[jfloff/pywfm](https://github.com/jfloff/docker-pywfm)** [![Docker Stars](https://img.shields.io/docker/stars/jfloff/pywfm.svg)][pywfm-hub] [![Docker Pulls](https://img.shields.io/docker/pulls/jfloff/pywfm.svg)][pywfm-hub] : image from the python wrapper for Steffen Rendle's factorization machines library libFM. +- **[bismuthfoundation/Bismuth-Docker](https://github.com/bismuthfoundation/Bismuth-Docker)** [![Docker Stars](https://img.shields.io/docker/stars/eggdrasyl/bismuth-node.svg)][busmuth-hub] [![Docker Pulls](https://img.shields.io/docker/pulls/eggdrasyl/bismuth-node.svg)][busmuth-hub] : node and associated services, from scratch crypto-currency with Python codebase. [alscipy-hub]: https://hub.docker.com/r/jfloff/alscipy/ +[pywfm-hub]: https://hub.docker.com/r/jfloff/pywfm/ +[busmuth-hub]: https://hub.docker.com/r/eggdrasyl/bismuth-node/ - +## Contribution +Feel free to contribute with whatever you feel like this image is missing. There is also some changes that happen often like, updating Alpine or Python versions. Do not forget that this repo folders mirror **Python** version and **_not_** Alpine versions. ## License The code in this repository, unless otherwise noted, is MIT licensed. See the `LICENSE` file in this repository. - -## TODO -At this moment with Alpine APK we are not able to install previous packages versions, i.e., its virtually impossible to provide multiple versions of Python. This is limits us to only provide the latest `python3` package that's available in Alpine APK. - -Ideally we would support a solution like the official `python` images does (see example [here](https://github.com/docker-library/python/blob/master/3.4/alpine/Dockerfile)), where the specific Python version is downloaded and compiled. Yet, I don't want to follow the pitfall of copy-pasting that solution, otherwise we end up with almost the same image. ***I'm requesting PRs on this issue, either by optimizing official solution, or other.*** diff --git a/build-all.sh b/build-all.sh index a9256d5..221a350 100755 --- a/build-all.sh +++ b/build-all.sh @@ -8,20 +8,21 @@ reset="$(tput sgr0)" build_with_status () { tag="$1" - echo -n "Building $tag" + printf "${b}Building ${tag} ... " docker build . -t $tag &> /dev/null if [[ $? -ne 0 ]]; then - echo "$bold$red BUILD FAILED$reset" + printf "\n${bold}${red}BUILD FAILED${reset}" exit 1 fi echo 'print("something")' | docker run --rm -i $tag &> /dev/null if [[ $? -ne 0 ]]; then - echo "$bold$red TEST FAILED$reset" + printf "${bold}${red}TEST FAILED${reset}" exit 1 else - echo "$bold$green SUCCESS$reset" + printf "${bold}${green}SUCCESS${reset}" fi + printf "\n" } # quiet versions @@ -29,8 +30,8 @@ pushd() { builtin pushd $1 > /dev/null; } popd() { builtin popd > /dev/null; } # Move to where the script is -HERE="$(dirname $(readlink -f $0 || realpath $0))" -cd $HERE +HERE="$( cd "$(dirname "$0")" ; pwd -P )" +cd "$HERE" # Find all the top-level dirs for version in $(find -maxdepth 1 -not -name '.*' -type d -printf '%P\n' | sort); do