Docker Skill
When to activate
- Writing or optimizing Dockerfiles for production
- Setting up multi-stage builds to reduce image size
- Writing Docker Compose files for local development
- Debugging container startup failures or layer cache issues
- Configuring non-root users, health checks, and image security
- Setting up .dockerignore for efficient builds
- Writing build scripts or CI/CD Docker build pipelines
When NOT to use
- Kubernetes manifests (use the Kubernetes skill)
- Buildpacks (Heroku, Cloud Native Buildpacks) — different build system
- Virtual machine provisioning (different abstraction level)
- Nix-based reproducible builds
Instructions
Production Dockerfile structure
Always use multi-stage builds for compiled languages and Node.js:
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Stage 2: Runtime — minimal image
FROM node:20-alpine AS runtime
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:8080/healthz || exit 1
CMD ["node", "server.js"]
Security rules
- Never run as root in production — always create and switch to a non-root user
- Never use
latest tag — pin to a specific version or digest
- Prefer Alpine or distroless base images over full Debian/Ubuntu
- Never copy
.env files into the image — pass secrets as runtime env vars
- Scan images with
docker scout or Trivy before pushing to production
Layer caching optimization
Order Dockerfile instructions from least-to-most frequently changing:
- Base image (changes rarely)
- System dependencies (
apt-get, apk add)
- Package manager files (
package.json, requirements.txt)
- Package install (
npm ci, pip install)
- Application code (
COPY . .) — changes most often, must be last
.dockerignore — always include
node_modules/
.git/
.env
.env.*
*.md
Dockerfile*
docker-compose*
.dockerignore
coverage/
.nyc_output/
__pycache__/
*.pyc
.pytest_cache/
Docker Compose for local development
services:
app:
build:
context: .
target: builder # Use build stage, not runtime stage locally
volumes:
- .:/app # Hot reload
- /app/node_modules # Don't overwrite container node_modules
ports:
- "8080:8080"
environment:
- NODE_ENV=development
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: dev_password # Dev only — never production
POSTGRES_DB: appdb
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 5s
timeout: 5s
retries: 5
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Common build failures
COPY fails silently if source doesn't exist — check .dockerignore isn't excluding needed files
- Layer cache invalidated unexpectedly — check if a
COPY before install steps is pulling in changed files
- Permission denied at runtime — check file ownership when using
COPY --from with a non-root user
Example
User: Write a production Dockerfile for a Python FastAPI app with multi-stage build, non-root user, and health check.
Expected output:
- Stage 1 (builder):
python:3.12-slim, install dependencies with pip install --no-cache-dir
- Stage 2 (runtime):
python:3.12-slim, non-root user, copy only wheels/deps from builder + app code
HEALTHCHECK hitting /healthz endpoint
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
.dockerignore covering __pycache__, .env, .git, *.pyc
1---2name: docker3description: Dockerfile best practices, multi-stage builds, Compose services, networking, volumes, build caching4---56# Docker Skill78## When to activate9- Writing or optimizing Dockerfiles for production10- Setting up multi-stage builds to reduce image size11- Writing Docker Compose files for local development12- Debugging container startup failures or layer cache issues13- Configuring non-root users, health checks, and image security14- Setting up .dockerignore for efficient builds15- Writing build scripts or CI/CD Docker build pipelines1617## When NOT to use18- Kubernetes manifests (use the Kubernetes skill)19- Buildpacks (Heroku, Cloud Native Buildpacks) — different build system20- Virtual machine provisioning (different abstraction level)21- Nix-based reproducible builds2223## Instructions2425### Production Dockerfile structure26Always use multi-stage builds for compiled languages and Node.js:2728```dockerfile29# Stage 1: Build30FROM node:20-alpine AS builder31WORKDIR /app32COPY package*.json ./33RUN npm ci --only=production3435# Stage 2: Runtime — minimal image36FROM node:20-alpine AS runtime37RUN addgroup -S appgroup && adduser -S appuser -G appgroup38WORKDIR /app39COPY --from=builder /app/node_modules ./node_modules40COPY . .41USER appuser42EXPOSE 808043HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \44 CMD wget -qO- http://localhost:8080/healthz || exit 145CMD ["node", "server.js"]46```4748### Security rules49- Never run as root in production — always create and switch to a non-root user50- Never use `latest` tag — pin to a specific version or digest51- Prefer Alpine or distroless base images over full Debian/Ubuntu52- Never copy `.env` files into the image — pass secrets as runtime env vars53- Scan images with `docker scout` or Trivy before pushing to production5455### Layer caching optimization56Order Dockerfile instructions from least-to-most frequently changing:571. Base image (changes rarely)582. System dependencies (`apt-get`, `apk add`)593. Package manager files (`package.json`, `requirements.txt`)604. Package install (`npm ci`, `pip install`)615. Application code (`COPY . .`) — changes most often, must be last6263### .dockerignore — always include64```65node_modules/66.git/67.env68.env.*69*.md70Dockerfile*71docker-compose*72.dockerignore73coverage/74.nyc_output/75__pycache__/76*.pyc77.pytest_cache/78```7980### Docker Compose for local development81```yaml82services:83 app:84 build:85 context: .86 target: builder # Use build stage, not runtime stage locally87 volumes:88 - .:/app # Hot reload89 - /app/node_modules # Don't overwrite container node_modules90 ports:91 - "8080:8080"92 environment:93 - NODE_ENV=development94 depends_on:95 db:96 condition: service_healthy9798 db:99 image: postgres:16-alpine100 environment:101 POSTGRES_USER: app102 POSTGRES_PASSWORD: dev_password # Dev only — never production103 POSTGRES_DB: appdb104 ports:105 - "5432:5432"106 healthcheck:107 test: ["CMD-SHELL", "pg_isready -U app -d appdb"]108 interval: 5s109 timeout: 5s110 retries: 5111 volumes:112 - postgres_data:/var/lib/postgresql/data113114volumes:115 postgres_data:116```117118### Common build failures119- `COPY` fails silently if source doesn't exist — check `.dockerignore` isn't excluding needed files120- Layer cache invalidated unexpectedly — check if a `COPY` before install steps is pulling in changed files121- Permission denied at runtime — check file ownership when using `COPY --from` with a non-root user122123## Example124125**User:** Write a production Dockerfile for a Python FastAPI app with multi-stage build, non-root user, and health check.126127**Expected output:**128- Stage 1 (builder): `python:3.12-slim`, install dependencies with `pip install --no-cache-dir`129- Stage 2 (runtime): `python:3.12-slim`, non-root user, copy only wheels/deps from builder + app code130- `HEALTHCHECK` hitting `/healthz` endpoint131- `CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]`132- `.dockerignore` covering `__pycache__`, `.env`, `.git`, `*.pyc`133134---