This skill MUST be loaded when ANY Dockerfile, docker-compose.yml, docker-compose.override.yml, .dockerignore, or Docker-related configuration file is being read, reviewed, edited, or created. Also use when the user asks to "create a Dockerfile", "write a multi-stage build", "add a .dockerignore", "set up docker-compose", "containerize an application", "optimize a Docker image", "reduce Docker image size", "fix Docker build issues", "add health checks to containers", "set up a local dev environment with Docker", "create a multi-service setup", "debug container startup", "configure Docker volumes", "set up database containers", or mentions Dockerfile, Docker Compose, multi-stage builds, container images, build context, layer caching, ENTRYPOINT, CMD, docker-compose.yml, Docker health checks, or container orchestration for local development.
This skill provides comprehensive guidance for writing production-ready Dockerfiles, .dockerignore files, and Docker Compose configurations for multi-service applications.
latest), use --frozen / flags for package managers.--ci| Use Case | Image | Why |
|---|---|---|
| Python apps | python:3.12-slim | glibc-based (asyncpg/numpy work), smaller than full |
| Node.js apps | node:22-slim | Consistent with CI, npm works natively |
| Node.js (minimal) | node:22-alpine | Smallest Node image, good for simple apps |
| Go apps | golang:1.23 build → gcr.io/distroless/static-debian12 run | Zero-dependency static binary |
| Static files | nginx:alpine | Tiny, battle-tested web server |
| PostgreSQL + pgvector | pgvector/pgvector:pg16 | pgvector pre-installed, avoids manual extension setup |
| Redis | redis:7-alpine | Minimal Redis with optional AOF persistence |
Why not alpine for Python? Alpine uses musl libc. Packages like asyncpg, numpy, and psycopg2 need glibc or require compilation from source, making builds slower and images potentially larger. Use slim (Debian-based, glibc) unless you have no C extensions.
FROM — Always pin to a specific minor version. Never use latest or bare major tags.
# Good
FROM python:3.12-slim AS builder
# Bad
FROM python:latest
FROM python:3
RUN — Consolidate commands with && to reduce layers. Clean up caches in the same layer they're created.
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
COPY vs ADD — Always use COPY unless you specifically need tar auto-extraction or remote URL fetching (rare). COPY is explicit and predictable.
ARG vs ENV — ARG exists only during build (not in final image). ENV persists into running containers. Use ARG for build-time-only values like NEXT_PUBLIC_API_URL.
ARG NEXT_PUBLIC_API_URL=http://localhost:8000
ENV NODE_ENV=production
EXPOSE — Documentation only. Does not actually publish ports. Always pair with -p in docker run or ports: in Compose.
ENTRYPOINT vs CMD — Use together: ENTRYPOINT for the executable, CMD for default arguments. Always use exec form (JSON array), not shell form.
# Correct: exec form — process gets PID 1, receives SIGTERM directly
ENTRYPOINT ["uvicorn", "api.main:app"]
CMD ["--host", "0.0.0.0", "--port", "8000"]
# Wrong: shell form — wraps in /bin/sh, SIGTERM goes to shell not app
ENTRYPOINT uvicorn api.main:app
USER — Always create and switch to a non-root user before CMD.
RUN groupadd -r app && useradd -r -g app -d /app -s /sbin/nologin app
USER app
WORKDIR — Use absolute paths. Creates the directory if it doesn't exist. Prefer over RUN mkdir.
HEALTHCHECK — Define health checks at the Dockerfile level. Works in standalone Docker, Compose, and K8s.
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
The most important Docker pattern. Separates build-time dependencies (compilers, build tools) from runtime, producing minimal final images.
# Stage 1: Builder — has build tools, compiles dependencies
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen --no-dev
# Stage 2: Runtime — only the app and its runtime dependencies
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY . .
ENV PATH="/app/.venv/bin:$PATH"
USER app
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
Key insight: The builder stage can be 500MB+ with gcc, make, and header files. The runtime stage copies only the compiled output, typically 100-200MB.
For language-specific multi-stage patterns (Python/uv, Node.js/Next.js, Go), see references/multi-stage-builds.md.
The .dockerignore file controls what Docker sends to the build daemon as "build context." Without it, Docker sends everything — including .git/ (often 100MB+), node_modules/, .venv/, and secrets.
Place one .dockerignore per build context — if your API and frontend have separate Dockerfiles with different build contexts, each needs its own .dockerignore.
.venv/
venv/
__pycache__/
*.pyc
*.pyo
*.egg-info/
dist/
build/
.git/
.env
.env.*
.vscode/
.idea/
*.md
tests/
.coverage
htmlcov/
.pytest_cache/
Dockerfile
.dockerignore
node_modules/
.next/
out/
.env
.env.*
.env.local
coverage/
*.tsbuildinfo
.vscode/
.idea/
*.md
Dockerfile
.dockerignore
.git/
.DS_Store
Thumbs.db
*.tmp
*.swp
*.log
docker-compose*.yml
k8s/
specs/
docs/