build-python-dockerfiles
SKILL.md
Build Python Dockerfiles
Use this skill to author Dockerfiles for Python projects using uv and multi-stage builds.
The default pattern is based on Hynek Schlawack's article Production-ready Python Docker Containers with uv.
Workflow
- Detect whether the project is packaged (installable) or unpackaged (run from copied source).
- Choose a base image strategy:
- Default to
python:<version>-slimfor generic Python services. - Use an OS image like
ubuntu:noblewhen system packages or org-standard base images matter. - Keep build and runtime on the same distro family.
- Avoid Alpine unless the user has a hard requirement.
- Default to
- Install dependencies before copying source so lockfile changes and app code changes stay in separate layers.
- Keep dependency sync and app install in separate
uv syncsteps. - Pick the runtime command pattern that matches the app type.
- Verify the final Dockerfile against the checklist in this file.
Required build patterns
- Use two stages:
buildandfinal. - Add layers in inverse order of likely change frequency.
- Install dependencies before copying source to maximize cache hits.
- Keep dependency sync and app install in separate
RUN uv syncsteps. - Use BuildKit cache mounts for the
uvcache. - Use
UV_PROJECT_ENVIRONMENT=/appand copy/appinto runtime image. - Byte-compile Python files for faster startup with
UV_COMPILE_BYTECODE=1. - Run as non-root in runtime.
- Prefer
uv sync --lockedfor deployment builds.
Default settings
- Copy
uvfromghcr.io/astral-sh/uv:<version>. - Set:
UV_LINK_MODE=copyUV_COMPILE_BYTECODE=1UV_PYTHON_DOWNLOADS=neverUV_PROJECT_ENVIRONMENT=/app
- Set
UV_PYTHONwhen the base image has multiple Python interpreters or whenuvcannot infer the intended one cleanly. - Use BuildKit cache mounts for the
uvcache, typically/root/.cache/uvor/root/.cache. - Never bake secrets into the Dockerfile via
ENV.
Baseline template
Use this as the default starting point for packaged applications:
# syntax=docker/dockerfile:1.9
FROM python:3.13-slim AS build
COPY /uv /uvx /bin/
ENV UV_LINK_MODE=copy \
UV_COMPILE_BYTECODE=1 \
UV_PYTHON_DOWNLOADS=never \
UV_PROJECT_ENVIRONMENT=/app
WORKDIR /src
COPY pyproject.toml uv.lock ./
RUN \
uv sync --locked --no-dev --no-install-project
COPY . /src
RUN \
uv sync --locked --no-dev --no-editable
FROM python:3.13-slim AS final
ENV PATH="/app/bin:$PATH"
STOPSIGNAL SIGINT
RUN groupadd --system app && useradd --system --gid app --home-dir /app app
COPY /app /app
USER app
WORKDIR /app
CMD ["python", "-m", "your_package"]
Keep the following adaptation rules in mind:
- If the project is not packaged, skip the second
uv syncstep and copy the source into the runtime image after copying/app. - If the project has heavy OS-level build dependencies, use a fuller build image and only install runtime libraries in the final image.
- If the project needs a shell entrypoint or signal handling wrapper, use an explicit
ENTRYPOINTscript and keepSTOPSIGNAL SIGINT. - When using an OS image instead of
python:<version>-slim, install the exact runtime Python packages explicitly and setUV_PYTHONif needed.
Entrypoint patterns
Pick the startup command that matches the project type.
ASGI (FastAPI, Starlette, Quart)
EXPOSE 8000
CMD ["uvicorn", "your_package.main:app", "--host", "0.0.0.0", "--port", "8000"]
Django (WSGI)
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "your_project.wsgi:application"]
Flask or generic WSGI
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "your_package.wsgi:app"]
Background worker
CMD ["python", "-m", "your_package.worker"]
CLI or batch task container
ENTRYPOINT ["python", "-m", "your_package.cli"]
Use JSON-array syntax for CMD and ENTRYPOINT.
Final checklist
Run this checklist before finalizing output:
- Multi-stage build is used.
uvis copied from the official image.- Dependency installation happens before source copy.
- Dependency sync and application install are separate when the app is packaged.
- A BuildKit cache mount is used for
uv. UV_PROJECT_ENVIRONMENT=/appis set and/appis copied into the runtime image.- The runtime image runs as a non-root user.
PATHincludes/app/binwhen executables live in the project environment.- Startup command is explicit and matches app type.
- No secrets are baked into the image.
- Optional but recommended:
STOPSIGNAL SIGINT. - Optional but recommended:
EXPOSEfor network services. - Optional but recommended: a
.dockerignorethat excludes VCS data, caches, build artifacts, and local virtualenvs.
Output contract
When generating a Dockerfile for a user:
- Return a complete Dockerfile.
- State assumptions briefly (Python version, packaged vs unpackaged, startup command).
- Add a short
.dockerignoresuggestion when missing.
Weekly Installs
3
Repository
baggiponte/skillsFirst Seen
7 days ago
Security Audits
Installed on
opencode3
gemini-cli3
github-copilot3
codex3
amp3
cline3