docker-build
Docker Build for Python/FastAPI Applications
Overview
Containerize Python and FastAPI applications using Docker with
production-grade multi-stage builds, layer caching, security hardening, and
Docker Compose orchestration. All patterns target Python 3.12+, use uv for
dependency management, and follow the principle of minimal, reproducible
images.
Refer to the asset templates bundled with this skill for ready-to-use starting points:
assets/Dockerfile.fastapi-- production multi-stage Dockerfileassets/Dockerfile.dev-- development Dockerfile with hot reloadassets/docker-compose.yml-- Compose stack with PostgreSQL and Redisassets/.dockerignore-- ignore rules for lean build contexts
Base Image Selection
Choose the base image according to the deployment target and dependency requirements.
python:3.12-slim (recommended)
The default choice for most FastAPI projects. Based on Debian Bookworm with a
minimal package set. Binary wheels from PyPI install without issues, and
system libraries such as libpq can be added via apt-get.
FROM python:3.12-slim AS base
python:3.12-alpine
Smaller download size, but Alpine uses musl libc. Packages with C extensions
(e.g., psycopg2, numpy, pandas) often fail to install or require
building from source, negating the size advantage. Avoid unless the
dependency tree is pure Python.
ghcr.io/astral-sh/uv:python3.12-bookworm-slim
Ships with uv pre-installed. Useful when the build should not fetch uv
at build time. The image is Debian-based and behaves like python:3.12-slim
otherwise.
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
Multi-Stage Build Pattern
Separate dependency installation from the final runtime image to keep the production image small and free of build tools.
Stage 1 -- Builder
Install uv, copy only the dependency manifests, and resolve/install
dependencies into a virtual environment. This stage may contain compilers and
header packages that must not ship in production.
FROM python:3.12-slim AS builder
COPY /uv /usr/local/bin/uv
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
COPY . .
RUN uv sync --frozen --no-dev
Stage 2 -- Runtime
Start from a clean slim image, copy only the virtual environment and application source from the builder, create a non-root user, and declare the entrypoint.
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY /app/.venv /app/.venv
COPY /app/src /app/src
ENV PATH="/app/.venv/bin:$PATH"
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
uv Integration in Docker
Installing uv
The fastest method is a static binary copy from the official image:
COPY /uv /usr/local/bin/uv
Alternatively, install via pip (slower, but works on any base):
RUN pip install --no-cache-dir uv
Dependency Resolution
Always pass --frozen to uv sync inside Docker so that the lockfile is
used as-is without resolution. This guarantees reproducible builds.
RUN uv sync --frozen --no-dev
For uv dependency management outside of Docker, consult the uv skill.
Layer Ordering for Cache Efficiency
Docker caches each layer. When a layer changes, every subsequent layer is rebuilt. Order instructions from least to most frequently changing:
- Base image and system packages
- Copy dependency manifests (
pyproject.toml,uv.lock) - Install dependencies (
uv sync) - Copy application source code
- Any final build steps
# Step 2 -- manifests change infrequently
COPY pyproject.toml uv.lock ./
# Step 3 -- re-runs only when manifests change
RUN uv sync --frozen --no-dev --no-install-project
# Step 4 -- changes on every code edit
COPY src/ src/
This ordering ensures that dependency installation is cached across most code changes, drastically reducing rebuild times.
Security Hardening
Non-Root User
Never run the application as root inside the container. Create a dedicated
user and switch to it before CMD.
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
For stricter security, use a numeric UID and no login shell:
RUN adduser --system --uid 1001 --no-create-home appuser
USER 1001
Minimal Packages
Do not install editors, debug tools, or documentation packages in the
production image. If build-time packages are necessary (e.g., gcc,
libpq-dev), install them only in the builder stage.
.dockerignore
Exclude files that must not enter the build context: version control
metadata, local environment files, test suites, caches, and documentation.
See assets/.dockerignore for a comprehensive template.
Pin Image Digests in CI
For reproducible CI builds, pin the base image to a digest:
FROM python:3.12-slim@sha256:<digest> AS builder
HEALTHCHECK Instruction
Define a health check so orchestrators (Docker Swarm, Compose, ECS) can detect unresponsive containers and restart them automatically.
HEALTHCHECK \
CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] || exit 1
If curl is available in the runtime image, prefer the simpler form:
HEALTHCHECK \
CMD curl -f http://localhost:8000/health || exit 1
Ensure the FastAPI application exposes a lightweight /health endpoint that
returns HTTP 200.
Environment Variables
Set these early in the Dockerfile to influence Python runtime behavior:
# Send stdout/stderr straight to the terminal without buffering
ENV PYTHONUNBUFFERED=1
# Prevent .pyc file creation inside the container
ENV PYTHONDONTWRITEBYTECODE=1
Application-specific variables (database URL, secret keys) should be injected
at runtime via docker run --env-file or the Compose env_file directive,
never baked into the image.
CMD with Exec Form
Always use the exec form (JSON array) for CMD so that uvicorn receives
signals directly from Docker and can shut down gracefully.
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Avoid the shell form (CMD uvicorn ...) because it wraps the process in
/bin/sh -c, which swallows SIGTERM and delays container stops.
For uvicorn production configuration (workers, timeouts, keep-alive),
consult the uvicorn skill.
Docker Compose
Use Docker Compose to orchestrate the application alongside backing services
such as PostgreSQL and Redis. See assets/docker-compose.yml for a complete
template.
Service Definitions
services:
app:
build:
context: .
dockerfile: Dockerfile.fastapi
ports:
- "8000:8000"
env_file:
- .env
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
Health Checks in Compose
Each service should declare a healthcheck so that depends_on with
condition: service_healthy works correctly.
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
Volumes
Persist database data across container recreations:
volumes:
postgres_data:
redis_data:
Networks
Isolate inter-service traffic on a dedicated bridge network:
networks:
backend:
driver: bridge
Development vs Production Dockerfiles
Maintain separate Dockerfiles for each environment.
Production (Dockerfile.fastapi)
- Multi-stage build
--no-devdependencies only- Source code copied into the image
- Non-root user, health check, exec-form CMD
Development (Dockerfile.dev)
- Single stage for simplicity
- All dependencies including dev extras (
pytest,ruff, etc.) - Source code mounted via a bind volume -- do not
COPYsource --reloadflag on uvicorn for hot reload
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
Override the production Compose file with a development variant:
# docker-compose.override.yml
services:
app:
build:
dockerfile: Dockerfile.dev
volumes:
- .:/app
Build Arguments
Use ARG to parameterize the build without creating multiple Dockerfiles.
ARG PYTHON_VERSION=3.12
FROM python:${PYTHON_VERSION}-slim AS builder
ARG APP_VERSION=0.0.0
LABEL org.opencontainers.image.version=${APP_VERSION}
Pass values at build time:
docker build --build-arg PYTHON_VERSION=3.13 --build-arg APP_VERSION=1.2.0 .
Volume Mounts for Development
Bind-mount the project directory into the container so that file changes on
the host are immediately visible inside the container, enabling uvicorn's
--reload watcher.
docker run -v "$(pwd)":/app -p 8000:8000 myapp-dev
In Compose:
services:
app:
volumes:
- .:/app
Avoid bind-mounting over the .venv directory. If the virtual environment
lives inside the project tree, use an anonymous volume to shadow it:
volumes:
- .:/app
- /app/.venv
Image Size Reduction Checklist
- Use
python:3.12-sliminstead of the fullpythonimage. - Apply multi-stage builds; keep compilers in the builder stage only.
- Combine
RUNcommands where logical to reduce layer count. - Pass
--no-cache-dirto pip or useuv(which never caches by default). - Remove apt lists after installing system packages:
RUN apt-get update && apt-get install -y --no-install-recommends pkg && rm -rf /var/lib/apt/lists/* - Add a thorough
.dockerignoreto minimize the build context. - Compile bytecode at build time (
UV_COMPILE_BYTECODE=1) and skip.pycgeneration at runtime (PYTHONDONTWRITEBYTECODE=1).
Multi-Architecture Builds
Build images for multiple platforms using docker buildx:
# Create a buildx builder
docker buildx create --name multiarch --use
# Build and push for amd64 and arm64
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .
When using multi-arch builds, ensure all base images support the target platforms. Avoid architecture-specific binaries in COPY instructions.
BuildKit Secrets
Mount secrets at build time without baking them into image layers:
# syntax=docker/dockerfile:1
RUN \
PIP_INDEX_URL=$(cat /run/secrets/pip_index_url) \
uv sync --frozen --no-dev
Pass the secret at build time:
docker build --secret id=pip_index_url,env=PIP_INDEX_URL .
Secrets are available only during the RUN instruction and never persist in the image history.
Common Labels
Apply OCI-standard labels for image metadata:
LABEL org.opencontainers.image.title="my-fastapi-app"
LABEL org.opencontainers.image.description="FastAPI application"
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.source="https://github.com/org/repo"
Cross-References
- For uvicorn production configuration (workers, timeouts, logging), consult
the
uvicornskill. - For uv dependency management (lockfiles, workspaces, scripts), consult the
uvskill.