@@ -53,34 +53,63 @@ RUN make frontend/img frontend/js/compile frontend/css/compile \
53
53
54
54
# ########################################################################
55
55
# Backend stuff
56
+ # Large chunks copy-pasted from https://hynek.me/articles/docker-uv/ :-)
56
57
57
58
FROM python:3.11-slim-bookworm AS backend_build
58
59
59
- ENV POETRY_VERSION=1.8.3
60
-
61
- ENV DEBIAN_FRONTEND=noninteractive
62
- ENV PYTHONDONTWRITEBYTECODE=0 PYTHONUNBUFFERED=1
63
-
64
- RUN apt-get update && apt-get install -y \
65
- python3-pip \
66
- python3-venv \
67
- python3-dev \
68
- python3-setuptools \
69
- python3-wheel \
70
- libpq-dev \
71
- && apt-get clean && rm -rf /var/lib/apt/lists/*
72
-
73
- RUN pip install --upgrade pip
74
- RUN pip install poetry==${POETRY_VERSION}
60
+ # The following does not work in Podman unless you build in Docker
61
+ # compatibility mode: <https://github.com/containers/podman/issues/8477>
62
+ # You can manually prepend every RUN script with `set -ex` too.
63
+ SHELL ["sh" , "-exc" ]
64
+
65
+ RUN <<EOT
66
+ apt-get update -qy
67
+ apt-get install -qyy \
68
+ -o APT::Install-Recommends=false \
69
+ -o APT::Install-Suggests=false \
70
+ build-essential \
71
+ ca-certificates \
72
+ curl
73
+ EOT
74
+
75
+ # Install uv.
76
+ # https://docs.astral.sh/uv/guides/integration/docker/
77
+ COPY --from=ghcr.io/astral-sh/uv:0.4.4 /uv /usr/local/bin/uv
75
78
76
79
RUN mkdir -p /app
77
80
WORKDIR /app
78
81
79
- RUN python -m venv --symlinks .venv
80
-
81
- COPY pyproject.toml poetry.lock ./
82
- RUN poetry install --only=main --no-root --no-interaction --no-ansi
83
-
82
+ # - Silence uv complaining about not being able to use hard links,
83
+ # - tell uv to byte-compile packages for faster application startups,
84
+ # - prevent uv from accidentally downloading isolated Python builds,
85
+ # - pick a Python,
86
+ # - and finally declare `/app/.venv` as the target for `uv sync`.
87
+ ENV UV_LINK_MODE=copy \
88
+ UV_COMPILE_BYTECODE=1 \
89
+ UV_PYTHON_DOWNLOADS=never \
90
+ UV_PYTHON=python3.11 \
91
+ UV_PROJECT_ENVIRONMENT=/app/.venv
92
+
93
+ # Prepare a virtual environment.
94
+ # This is cached until the Python version changes above.
95
+ RUN --mount=type=cache,target=/root/.cache \
96
+ uv venv $UV_PROJECT_ENVIRONMENT
97
+
98
+ # Synchronize DEPENDENCIES without the application itself.
99
+ # This layer is cached until uv.lock or pyproject.toml change.
100
+ # Since there's no point in shipping lock files, we move them
101
+ # into a directory that is NOT copied into the runtime image.
102
+ COPY pyproject.toml /_lock/
103
+ COPY uv.lock /_lock/
104
+ RUN --mount=type=cache,target=/root/.cache <<EOT
105
+ cd /_lock
106
+ uv sync \
107
+ --frozen \
108
+ --no-dev \
109
+ --no-install-project
110
+ EOT
111
+
112
+
84
113
FROM python:3.11-slim-bookworm AS assets_download
85
114
86
115
# By having a separate build stage for downloading assets, we can cache them
@@ -100,21 +129,29 @@ RUN python scripts/download_assets.py
100
129
101
130
FROM python:3.11-slim-bookworm AS backend_run
102
131
103
- ENV DEBIAN_FRONTEND=noninteractive
104
- ENV PYTHONDONTWRITEBYTECODE=0 PYTHONUNBUFFERED=1
132
+ SHELL ["sh" , "-exc" ]
105
133
106
- RUN apt-get update && apt-get install -y \
107
- libpq5 \
108
- && apt-get clean && rm -rf /var/lib/apt/lists/*
134
+ # Add the application virtualenv to search path.
135
+ ENV PATH=/app/.venv/bin:$PATH
109
136
110
- RUN mkdir -p /app
111
- WORKDIR /app
137
+ # Allow our "src/" folder to be seen as a package root:
138
+ ENV PYTHONPATH= /app/src
112
139
113
- RUN addgroup -gid 1001 webapp
114
- RUN useradd --gid 1001 --uid 1001 webapp
115
- RUN chown -R 1001:1001 /app
140
+ # Let's make sure that our Django app sees "/app" as its base dir, rather than
141
+ # a resolved symlink in the venv. This is important for the collectstatic command.
142
+ ENV DJANGO_BASE_DIR=/app
143
+
144
+ # Don't run our app as root.
145
+ RUN <<EOT
146
+ groupadd --gid 1001 app
147
+ useradd --uid 1001 -g app -N app -d /app
148
+ EOT
149
+
150
+ USER app
151
+ WORKDIR /app
116
152
117
153
COPY --chown=1001:1001 scripts scripts
154
+ COPY --chown=1001:1001 Makefile pyproject.toml manage.py LICENSE ./
118
155
COPY --chown=1001:1001 src src
119
156
120
157
COPY --chown=1001:1001 --from=frontend_build /app/src/apps/webui/static src/apps/webui/static
@@ -127,24 +164,24 @@ COPY --chown=1001:1001 --from=assets_download /app/src/apps/chess/static/chess/j
127
164
COPY --chown=1001:1001 --from=assets_download /app/src/apps/chess/static/chess/units src/apps/chess/static/chess/units
128
165
COPY --chown=1001:1001 --from=assets_download /app/src/apps/chess/static/chess/symbols src/apps/chess/static/chess/symbols
129
166
130
- COPY --chown=1001:1001 Makefile pyproject.toml LICENSE ./
131
-
132
- ENV PATH="/app/.venv/bin:${PATH}"
133
167
RUN python -V
168
+ RUN python -Im site
169
+ # Smoke test:
170
+ RUN DJANGO_SETTINGS_MODULE=project.settings.docker_build \
171
+ .venv/bin/python manage.py shell -c "from apps.daily_challenge.models import DailyChallenge"
134
172
135
- USER 1001:1001
136
-
137
- # Install the project's packages (defined in pyproject.toml) in editable mode:
138
- RUN pip install -e .
139
-
173
+ # Collect static files.
140
174
RUN DJANGO_SETTINGS_MODULE=project.settings.docker_build \
141
- .venv/bin/python src/ manage.py collectstatic --noinput
175
+ .venv/bin/python manage.py collectstatic --noinput
142
176
177
+ # Ok, let's get ready to run that server!
143
178
EXPOSE 8080
144
179
145
180
ENV DJANGO_SETTINGS_MODULE=project.settings.production
146
181
147
- ENV GUNICORN_CMD_ARGS="--bind :8080 --workers 2 --max-requests 120 --max-requests-jitter 20 --timeout 8"
182
+ ENV GUNICORN_CMD_ARGS="--bind 0.0.0.0 :8080 --workers 2 --max-requests 120 --max-requests-jitter 20 --timeout 8"
148
183
149
184
RUN chmod +x scripts/start_server.sh
150
- CMD ["scripts/start_server.sh" ]
185
+ # See <https://hynek.me/articles/docker-signals/>.
186
+ STOPSIGNAL SIGINT
187
+ ENTRYPOINT ["scripts/start_server.sh" ]
0 commit comments