Skip to content

Commit 8d4b4ce

Browse files
committed
update dockerfile build stages to optimize build caching of the devcontiner stage
1 parent 878a215 commit 8d4b4ce

File tree

4 files changed

+70
-71
lines changed

4 files changed

+70
-71
lines changed

Dockerfile

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,9 @@ ARG PYTHON_VERSION=3.12-slim-bookworm
44
FROM python:${PYTHON_VERSION} as base
55
LABEL maintainer="Mike Glenn <mglenn@ilude.com>"
66

7-
# User management setup (previously in user_base stage)
8-
ARG PUID=${PUID:-1000}
9-
ARG PGID=${PGID:-1000}
10-
11-
ARG USER=anvil
12-
ENV USER=${USER}
13-
14-
ARG PROJECT_NAME
15-
ENV PROJECT_NAME=${PROJECT_NAME}
16-
17-
ARG PROJECT_PATH=/srv
18-
ENV PROJECT_PATH=${PROJECT_PATH}
19-
20-
ARG ONBOARD_PORT=9830
21-
ENV ONBOARD_PORT=${ONBOARD_PORT}
22-
23-
ENV HOME=/home/${USER}
24-
ARG TERM_SHELL=zsh
25-
ENV TERM_SHELL=${TERM_SHELL}
26-
277
ARG TZ=America/New_York
288
ENV TZ=${TZ}
299

30-
### Remove legacy vendor/deps path usage; rely on system site-packages managed by uv
31-
ENV PYTHONUNBUFFERED=TRUE
32-
ENV UV_LINK_MODE=copy
33-
ENV UV_SYSTEM_PYTHON=1
34-
# Install project dependencies into the system Python prefix instead of a project .venv
35-
# See: https://docs.astral.sh/uv/concepts/projects/config/#project-environment-path
36-
ENV UV_PROJECT_ENVIRONMENT=/usr/local
37-
# Allow modifying the system environment inside containers
38-
ENV UV_BREAK_SYSTEM_PACKAGES=1
39-
4010
ENV DEBIAN_FRONTEND=noninteractive
4111
ENV DEBCONF_NONINTERACTIVE_SEEN=true
4212

@@ -64,14 +34,36 @@ ENV LC_ALL en_US.UTF-8
6434
# Install uv
6535
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
6636

37+
# ----------------------------------------------------------------------
38+
# User setup
39+
# Sets up the non-root user, group, home directory, project path, and shell
40+
# This section is responsible for creating the runtime user and permissions
41+
# ----------------------------------------------------------------------
42+
43+
# User management setup (previously in user_base stage)
44+
ARG PUID=${PUID:-1000}
45+
ARG PGID=${PGID:-1000}
46+
47+
ARG USER=anvil
48+
ENV USER=${USER}
49+
50+
ENV HOME=/home/${USER}
51+
52+
ARG PROJECT_PATH=/srv
53+
ENV PROJECT_PATH=${PROJECT_PATH}
54+
55+
WORKDIR $PROJECT_PATH
56+
6757
RUN sed -i 's/UID_MAX .*/UID_MAX 100000/' /etc/login.defs && \
6858
groupadd --gid ${PGID} ${USER} && \
69-
useradd --uid ${PUID} --gid ${PGID} -s /bin/${TERM_SHELL} -m ${USER} && \
59+
useradd --uid ${PUID} --gid ${PGID} -s /bin/sh -m ${USER} && \
7060
mkdir -p ${PROJECT_PATH} && \
7161
chown -R ${USER}:${USER} ${PROJECT_PATH} && \
72-
chown -R ${USER}:${USER} ${HOME} && \
73-
# set the shell for root too
74-
chsh -s /bin/${TERM_SHELL}
62+
chown -R ${USER}:${USER} ${HOME}
63+
64+
# ----------------------------------------------------------------------
65+
# --- docker-entrypoint setup section ---
66+
# ----------------------------------------------------------------------
7567

7668
COPY --chmod=755 <<-"EOF" /usr/local/bin/docker-entrypoint.sh
7769
#!/bin/bash
@@ -105,13 +97,26 @@ echo "Running: $@"
10597
exec "$@"
10698
EOF
10799

108-
WORKDIR $PROJECT_PATH
109100
ENTRYPOINT [ "/usr/local/bin/docker-entrypoint.sh" ]
110101

111-
##############################
112-
# Begin build
113-
##############################
114-
FROM base as build
102+
103+
104+
### Remove legacy vendor/deps path usage; rely on system site-packages managed by uv
105+
ENV PYTHONUNBUFFERED=TRUE
106+
ENV UV_LINK_MODE=copy
107+
ENV UV_SYSTEM_PYTHON=1
108+
# Install project dependencies into the system Python prefix instead of a project .venv
109+
# See: https://docs.astral.sh/uv/concepts/projects/config/#project-environment-path
110+
ENV UV_PROJECT_ENVIRONMENT=/usr/local
111+
# Allow modifying the system environment inside containers
112+
ENV UV_BREAK_SYSTEM_PACKAGES=1
113+
114+
ENV ONBOARD_PORT=${ONBOARD_PORT:-9830}
115+
116+
# ----------------------------------------------------------------------
117+
# --- Build-base image stage
118+
# ----------------------------------------------------------------------
119+
FROM base as build-base
115120

116121
# Install build dependencies needed for compiling Python packages
117122
RUN --mount=type=cache,target=/var/cache/apt \
@@ -141,7 +146,10 @@ RUN --mount=type=cache,target=/var/cache/apt \
141146
apt-get autoclean -y && \
142147
rm -rf /var/lib/apt/lists/*
143148

144-
WORKDIR ${PROJECT_PATH}
149+
# ----------------------------------------------------------------------
150+
# --- Build-base image stage
151+
# ----------------------------------------------------------------------
152+
FROM build-base as build
145153

146154
# Copy lockfile and root pyproject for reproducible resolution
147155
COPY uv.lock* pyproject.toml ${PROJECT_PATH}/
@@ -154,9 +162,10 @@ RUN --mount=type=cache,target=/root/.cache/uv \
154162
--mount=type=cache,target=/root/.cache/pip \
155163
uv sync --no-editable --no-dev
156164

157-
##############################
158-
# Begin production
159-
##############################
165+
166+
# ----------------------------------------------------------------------
167+
# --- Production image stage ---
168+
# ----------------------------------------------------------------------
160169
FROM base as production
161170

162171
# Copy application code into the project directory root (creates ${PROJECT_PATH}/app)
@@ -179,23 +188,20 @@ RUN mkdir -p ${PROJECT_PATH}/static/icons && \
179188
# Run the app with gunicorn via uv without a shell
180189
CMD ["uv", "run", "--no-sync", "-m", "gunicorn", "run:app", "--bind", "0.0.0.0:9830", "--access-logfile", "-", "--error-logfile", "-"]
181190

182-
##############################
183-
# Begin devcontainer
184-
##############################
185-
FROM build as devcontainer
191+
# ----------------------------------------------------------------------
192+
# --- development os packages image stage ---
193+
# ----------------------------------------------------------------------
194+
FROM build-base as development-base
186195

187196
RUN --mount=type=cache,target=/var/cache/apt \
188197
--mount=type=cache,target=/var/lib/apt/lists \
189198
apt-get update && apt-get install -y --no-install-recommends \
190-
bash \
191199
bash-completion \
192-
build-essential \
193200
coreutils \
194201
docker.io \
195202
dnsutils \
196203
exa \
197204
gh \
198-
git \
199205
gnuplot \
200206
gnuplot-x11 \
201207
graphviz \
@@ -204,17 +210,11 @@ RUN --mount=type=cache,target=/var/cache/apt \
204210
iputils-ping \
205211
jq \
206212
less \
207-
libjpeg-dev \
208-
libpng-dev \
209213
libpq-dev \
210-
libssl-dev \
211-
libxml2-dev \
212-
libxslt-dev \
213214
libzmq3-dev \
214215
make \
215216
nodejs \
216217
npm \
217-
openssh-client \
218218
passwd \
219219
python3-pip \
220220
python3-setuptools \
@@ -235,7 +235,18 @@ RUN --mount=type=cache,target=/var/cache/apt \
235235
apt-get autoclean -y && \
236236
rm -rf /var/lib/apt/lists/* && \
237237
echo ${USER} ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/${USER} && \
238-
chmod 0440 /etc/sudoers.d/${USER}
238+
chmod 0440 /etc/sudoers.d/${USER} && \
239+
# set the shell for $USER and root
240+
chsh -s "$(which zsh)" ${USER} && \
241+
chsh -s "$(which zsh)" root
242+
243+
# ----------------------------------------------------------------------
244+
# --- development os packages image stage ---
245+
# ----------------------------------------------------------------------
246+
FROM development-base as devcontainer
247+
248+
# Copy lockfile and root pyproject for reproducible resolution
249+
COPY uv.lock* pyproject.toml ${PROJECT_PATH}/
239250

240251
# Install Python dependencies into the system environment (run as root)
241252
RUN --mount=type=cache,target=/tmp/.cache/uv \
@@ -245,8 +256,6 @@ RUN --mount=type=cache,target=/tmp/.cache/uv \
245256
chown -R $USER:$USER /usr/local/lib/python3.12/site-packages/ && \
246257
chown -R $USER:$USER /usr/local/bin
247258

248-
# Copy application code after dependency installation (keep package layout)
249-
COPY --chown=${USER}:${USER} app ${PROJECT_PATH}/
250259

251260
# Switch to non-root user for development
252261
USER ${USER}

pyproject.toml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,6 @@ line-length = 88
3737
skip-string-normalization = true
3838
target-version = ['py312']
3939

40-
[tool.setuptools]
41-
py-modules = ["run"]
42-
packages = ["app"]
43-
44-
#[tool.uv]
45-
# Using a single-project layout; default packaging behavior is enabled
46-
47-
[build-system]
48-
requires = ["setuptools>=45", "wheel", "uv"]
49-
build-backend = "setuptools.build_meta"
50-
5140
[dependency-groups]
5241
dev = [
5342
"behave",

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ python_paths = .
55
[pytest]
66
; By default exclude integration tests. Run with `pytest -q -m "integration"` to include them.
77
addopts = --strict-markers --strict-config --verbosity=2 -m "not integration"
8+
pythonpath = .
89
markers = integration: mark a test as an integration test that may require external services

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)