Skip to content

Commit 9a8b8d3

Browse files
Update dockerfile/compose
And entrypoint
1 parent 102e84b commit 9a8b8d3

File tree

3 files changed

+110
-143
lines changed

3 files changed

+110
-143
lines changed

Dockerfile

Lines changed: 60 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
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
1212
ARG DEBIAN_FRONTEND=noninteractive
@@ -15,155 +15,85 @@ ARG DEBIAN_FRONTEND=noninteractive
1515
RUN 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
14759
ENV LANG C.UTF-8
14860
ENV LC_ALL C.UTF-8
14961
ENV PYTHONDONTWRITEBYTECODE 1
15062
ENV 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

16282
WORKDIR /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"]

docker-compose.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@ version: "3.9"
33
services:
44
app:
55
container_name: hello-cont
6-
image: docker_python
7-
tty: false # false for `entrypoint` in Dockerfile
8-
stdin_open: false # false for `entrypoint` in Dockerfile
9-
env_file:
10-
- ./.env
11-
volumes:
12-
- .:/home/appuser/app
13-
ports:
14-
- 3000:3000
6+
image: python:3.10.9-slim-bullseye
7+
# tty: false # false for `entrypoint` in Dockerfile
8+
# stdin_open: false # false for `entrypoint` in Dockerfile
159
build:
1610
context: ./
1711
dockerfile: ./Dockerfile
12+
volumes:
13+
- .:/app
14+
ports:
15+
- ${PORT:-8000}:8000

startup.sh

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,47 @@
11
#!/usr/bin/env sh
22

3-
export VIRTUAL_ENV="/opt/venv"
4-
export PATH="${VIRTUAL_ENV}/bin:$HOME/.asdf/bin:$HOME/.asdf/shims:$PATH"
3+
# shellcheck disable=SC1091,SC2034,SC2086,SC2153,SC2236,SC3037,SC3045,SC2046
54

6-
# source .venv/bin/activate
5+
if [ "$(uname -s)" = "Darwin" ]; then
6+
export VENV=".venv"
7+
else
8+
export VENV="/opt/venv"
9+
fi
10+
export PATH="${VENV}/bin:$HOME/.asdf/bin:$HOME/.asdf/shims:$PATH"
711

8-
gunicorn -w 2 -k uvicorn.workers.UvicornWorker main:app -b 0.0.0.0:${PORT:-3000} --log-file -
12+
# . "${VENV}/bin/activate"
13+
14+
# BASE_DIR="$(dirname "$(readlink -f "$0")")"
15+
# SRV_DIR="${BASE_DIR}/app/commerce"
16+
17+
move_port() {
18+
echo "Port $1 is in use, trying $PORT"
19+
while [ ! -z "$(lsof -i :$PORT | grep LISTEN | awk '{print $2}')" ]; do
20+
echo "Port $PORT is in use, trying $((PORT+1))"
21+
PORT=$((PORT+1))
22+
done
23+
echo "Port $PORT is available. Using it instead of $1"
24+
}
25+
26+
port_check() {
27+
if [ -z "$1" ]; then
28+
PORT=8000
29+
elif [ "$1" -gt 0 ] 2>/dev/null; then
30+
PORT="$1"
31+
fi
32+
[ -z "$(lsof -i :$PORT | grep LISTEN | awk '{print $2}')" ] || move_port "$PORT"
33+
}
34+
35+
server() {
36+
# gunicorn/uvicorn
37+
gunicorn -w 2 -k uvicorn.workers.UvicornWorker main:app -b "0.0.0.0:${PORT}" --log-file -
38+
39+
# django
40+
# python "${SRV_DIR}/manage.py" runserver
41+
}
42+
43+
main() {
44+
port_check "$@"
45+
server
46+
}
47+
main "$@"

0 commit comments

Comments
 (0)