11# SOURCES
2- # https://github.com/alexdmoss/distroless-python
3- # https://gitlab.com/n.ragav/python-images/-/tree/master/distroless
2+ # https://denisbrogg.hashnode.dev/efficient-python-docker-image-from-any-poetry-project
3+ # https://binx.io/2022/06/13/poetry-docker/
4+ # https://github.com/python-poetry/poetry/discussions/1879#discussioncomment-216865
45
56# full semver just for python base image
6- ARG PYTHON_VERSION=3.10.8
7+ ARG PYTHON_VERSION=3.10.9
78
8- # several optimisations in python-slim images already, benefit from these
9- FROM python:${PYTHON_VERSION}-slim-bullseye AS builder-image
9+ FROM python:${PYTHON_VERSION}-slim-bullseye AS builder
1010
1111# avoid stuck build due to user prompt
1212ARG DEBIAN_FRONTEND=noninteractive
@@ -15,155 +15,85 @@ ARG DEBIAN_FRONTEND=noninteractive
1515RUN apt -qq update \
1616 && apt -qq install \
1717 --no-install-recommends -y \
18- autoconf \
19- automake \
20- build-essential \
21- ca-certificates \
2218 curl \
2319 gcc \
24- libbz2-dev \
25- libffi7 \
26- libffi-dev \
27- liblzma-dev \
28- libncurses-dev \
2920 libpq-dev \
30- libreadline-dev \
31- libsqlite3-dev \
32- libssl-dev \
33- libtool \
34- libxslt-dev \
35- libyaml-dev \
36- locales \
37- lzma \
38- sqlite3 \
39- unixodbc-dev \
40- zlib1g \
21+ python3-dev \
4122 && rm -rf /var/lib/apt/lists/*
4223
43- # Set locale
44- RUN locale-gen en_US.UTF-8
45- ENV LANG=en_US.UTF-8
46- ENV LANGUAGE=en_US:en
47- ENV LC_ALL=en_US.UTF-8
24+ # pip env vars
25+ ENV PIP_NO_CACHE_DIR=off
26+ ENV PIP_DISABLE_PIP_VERSION_CHECK=on
27+ ENV PIP_DEFAULT_TIMEOUT=100
4828
49- # TODO: debug heroku var interpolation (intermediate containers per ENV??)
50- # setup standard non-root user for use downstream
51- ENV USERNAME=appuser
52- ENV USER_GROUP=${USERNAME}
53- ENV HOME=/home/${USERNAME}
54-
55- RUN groupadd ${USERNAME}
56- RUN useradd -m ${USERNAME} -g ${USERNAME}
57-
58- # setup user environment
59- ENV PATH="$HOME/.local/bin:$PATH"
60-
61- WORKDIR /home/${USERNAME}
62- USER ${USERNAME}
29+ # poetry env vars
30+ ENV POETRY_HOME="/opt/poetry"
31+ ENV POETRY_VERSION=1.3.2
32+ ENV POETRY_VIRTUALENVS_IN_PROJECT=true
33+ ENV POETRY_NO_INTERACTION=1
6334
64- # poetry for use elsewhere as builder image
65- RUN pip install --user --upgrade pip \
66- && pip install --user --no-cache-dir --upgrade virtualenv poetry
35+ # path
36+ ENV VENV= "/opt/venv"
37+ ENV PATH= "$POETRY_HOME/bin:$VENV/bin:$PATH"
6738
68- COPY --chown=${USERNAME} pyproject.toml poetry.lock ./
69- RUN poetry config virtualenvs.in-project true \
70- && poetry config virtualenvs.options.always-copy true \
71- && poetry install
39+ WORKDIR /app
40+ COPY . .
7241
73- # # QA
74- # CMD ["/bin/bash"]
42+ RUN python -m venv $VENV \
43+ && . "${VENV}/bin/activate" \
44+ && python -m pip install "poetry==${POETRY_VERSION}" \
45+ && poetry install --no-ansi --no-root --without dev
7546
76- # build from distroless C or cc:debug, because lots of Python depends on C
77- FROM gcr.io/distroless/cc AS distroless
78-
79- # arch: x86_64-linux-gnu / aarch64-linux-gnu
80- ARG CHIPSET_ARCH=${CHIPSET_ARCH:-x86_64-linux-gnu}
81-
82- # required by lots of packages - e.g. six, numpy, asgi, wsgi, gunicorn
83- # libz.so.1, libexpat.so.1, libbz2.so, libffi.so.7
84- COPY --from=builder-image /etc/ld.so.cache /etc/
85-
86- # TODO: curl-specific libs (copying whole /lib and /usr/lib adds ~50MB to image)
87- # libcurl.so.4, libnghttp2.so.14, libidn2.so.0, librtmp.so.1, libssh2.so.1, libpsl.so.5
88- COPY --from=builder-image /lib/${CHIPSET_ARCH}/ /lib/${CHIPSET_ARCH}/
89- COPY --from=builder-image /usr/lib/${CHIPSET_ARCH}/ /lib/${CHIPSET_ARCH}/
90-
91- # non-root user setup
92- ARG USERNAME=appuser
93- ARG PYTHON_VERSION=3.10
94- ENV HOME=/home/${USERNAME}
95- ENV VENV="${HOME}/.venv"
96-
97- # import useful bins from busybox image
98- COPY --from=busybox:latest \
99- /bin/cat \
100- /bin/cut \
101- /bin/date \
102- /bin/find \
103- /bin/ls \
104- /bin/rm \
105- /bin/sed \
106- /bin/sh \
107- /bin/uname \
108- /bin/vi \
109- /bin/which \
110- /bin/
111- COPY --from=busybox:uclibc /bin/env /usr/bin/env
112- COPY --from=builder-image /usr/bin/curl /bin/curl
47+ FROM python:${PYTHON_VERSION}-slim-bullseye AS runner
11348
11449# setup standard non-root user for use downstream
115- ENV USERNAME=appuser
116- ENV USER_GROUP=${USERNAME}
117- ENV HOME=/home/${USERNAME}
118-
119- RUN echo "${USERNAME}:x:1000:${USERNAME}" >> /etc/group
120- RUN echo "${USERNAME}:x:1001:" >> /etc/group
121- RUN echo "${USERNAME}:x:1000:1001::${HOME}:" >> /etc/passwd
122-
123- # copy app and virtual environment
124- COPY --chown=${USERNAME} . /app
125- COPY --from=builder-image --chown=${USERNAME} "$VENV" "$VENV"
126- COPY --from=builder-image /usr/local/lib/ /usr/local/lib/
127- COPY --from=builder-image /usr/local/bin/python /usr/local/bin/python
128-
129- ENV PATH="/usr/local/bin:${HOME}/.local/bin:/bin:/usr/bin:${VENV}/bin:${VENV}/lib/python${PYTHON_VERSION}/site-packages:/usr/share/doc:$PATH"
130-
131- # remove dev bins (need sh to run `startup.sh`)
132- RUN rm /bin/cat /bin/find /bin/ls /bin/rm /bin/vi /bin/which
133-
134- # # QA
135- # CMD ["/bin/sh"]
136-
137- FROM distroless AS runner-image
138-
139- ARG PYTHON_VERSION=3.10
140- ARG USERNAME=appuser
141- ENV HOME=/home/${USERNAME}
142- ENV VENV="${HOME}/.venv"
50+ ENV USER_NAME=appuser
51+ ENV USER_GROUP=appuser
52+ ENV HOME="/home/${USER_NAME}"
53+ ENV HOSTNAME="${HOST:-localhost}"
54+ ENV VENV="/opt/venv"
14355
144- ENV PATH="/usr/local/ bin:${HOME}/.local/bin:/bin :/usr/bin:${VENV}/ bin:${VENV}/lib/python${PYTHON_VERSION}/site-packages :/usr/share/doc:$PATH"
56+ ENV PATH="${VENV}/ bin:${VENV}/lib/python${PYTHON_VERSION}/site-packages :/usr/local/ bin:${HOME}/.local/ bin:/bin:/usr/bin :/usr/share/doc:$PATH"
14557
14658# standardise on locale, don't generate .pyc, enable tracebacks on seg faults
14759ENV LANG C.UTF-8
14860ENV LC_ALL C.UTF-8
14961ENV PYTHONDONTWRITEBYTECODE 1
15062ENV PYTHONFAULTHANDLER 1
15163
152- # workers per core (https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/blob/master/README.md#web_concurrency)
153- ENV WEB_CONCURRENCY=1
64+ # workers per core
65+ # https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/blob/master/README.md#web_concurrency
66+ ENV WEB_CONCURRENCY=2
15467
155- COPY --from=busybox:uclibc /bin/chown /bin/chown
156- COPY --from=busybox:uclibc /bin/rm /bin/rm
68+ RUN groupadd ${USER_NAME} \
69+ && useradd -m ${USER_NAME} -g ${USER_GROUP}
15770
158- RUN chown -R ${USERNAME}:${USERNAME} /app \
159- && chown -R ${USERNAME}:${USERNAME} ${VENV} \
160- && rm /bin/chown /bin/rm
71+ # avoid stuck build due to user prompt
72+ ARG DEBIAN_FRONTEND=noninteractive
73+
74+ # install dependencies
75+ RUN apt -qq update \
76+ && apt -qq install \
77+ --no-install-recommends -y \
78+ curl \
79+ lsof \
80+ && rm -rf /var/lib/apt/lists/*
16181
16282WORKDIR /app
83+ COPY --chown=${USER_NAME} . .
84+ COPY --from=builder --chown=${USER_NAME} "$VENV" "$VENV"
16385
164- USER ${USERNAME}
86+ # TODO: debug `find` calls that break on missing dirs
87+ # COPY ./harden /usr/sbin/harden
88+ # RUN /usr/sbin/harden
89+
90+ USER ${USER_NAME}
91+
92+ # listening port (not published)
93+ EXPOSE 3000
16594
16695# ENTRYPOINT ["python", "main.py"]
167- # CMD ["gunicorn", "-c", "config/gunicorn.conf.py", "main:app"]
168- # CMD ["/bin/sh", "startup.sh"]
169- CMD ["/bin/sh" ]
96+ ENTRYPOINT ["/bin/sh" , "startup.sh" ]
97+ # CMD ["5000"]
98+ # CMD ["gunicorn", "-c", "gunicorn.conf.py", "main:app"]
99+ # CMD ["/bin/bash"]
0 commit comments