Skip to content

Dockerfile Standard

Container repositories use multi-stage Dockerfiles. A typical structure has dependency/build, CI/test, and runtime stages. Only the final runtime stage is published.

FROM ghcr.io/astral-sh/uv:0.11.28 AS uv
FROM python:3.12-slim-bookworm AS dependencies
WORKDIR /app
COPY --from=uv /uv /uvx /bin/
COPY pyproject.toml uv.lock ./
RUN uv sync --locked --no-dev --no-install-project
FROM dependencies AS ci
COPY . .
RUN uv sync --locked --all-groups \
&& uv run ruff format --check . \
&& uv run ruff check . \
&& uv run mypy . \
&& uv run pytest --cov --cov-report=xml
FROM python:3.12-slim-bookworm AS runtime
RUN useradd --create-home --uid 10001 nexa
COPY --from=dependencies /app/.venv /app/.venv
COPY --chown=nexa:nexa app /app/app
ENV PATH="/app/.venv/bin:$PATH"
USER 10001
CMD ["python", "-m", "app"]

A Node runtime follows the same principle:

FROM node:24-bookworm-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:24-bookworm-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/index.js"]
  • Bookworm Slim is the default approved base family.
  • Multi-stage builds are required unless an exception is approved.
  • The CI/test stage is never published as the product image.
  • Runtime images exclude compilers, caches, tests, source-control metadata, and development dependencies.
  • Containers run as a non-root user.
  • Secrets and registry credentials never enter image layers.
  • Health behavior and graceful termination are implemented where applicable.
  • Python images use the pinned uv 0.11.28 builder and a committed uv.lock; Poetry and requirements-file images are migration exceptions.
  • Alpine or another OS family requires an ADR or approved exception.