Claude Code for Docker — Dockerfile, Compose & Container Workflows
Docker is the universal packaging layer for every language and cloud. This guide shows how to configure Claude Code to generate production-grade Dockerfiles, multi-stage builds, and docker-compose stacks — and how to debug containers without leaving your terminal.
CLAUDE.md Template for Containerized Projects
Tell Claude Code your container constraints up front so every generated Dockerfile follows your standards automatically.
# CLAUDE.md — Containerized Service
## Build commands
- docker build -t myapp:dev .
- docker compose up -d
- docker compose logs -f app
- docker compose down -v # full teardown including volumes
## Container requirements
- Base image: node:22-alpine (or python:3.12-slim for Python services)
- Multi-stage build required for all production Dockerfiles
- Final stage must run as non-root (USER node or USER appuser)
- No secrets in Dockerfile — use runtime env vars or secrets mounts
- .dockerignore must exclude: node_modules, .git, .env, dist, __pycache__
## Compose topology
- Services: app (port 3000), postgres:16-alpine, redis:7-alpine
- Custom bridge network: app_network
- Named volumes for postgres data: pg_data
- Health checks required on postgres and redis
## Image size targets
- Development image: < 500 MB acceptable
- Production image: < 150 MB target (use multi-stage, alpine base)
Generating a Production Dockerfile (Multi-Stage)
Prompt Claude Code to generate a complete multi-stage Dockerfile for your service. The more context in CLAUDE.md, the fewer iterations needed.
# Example prompt
"Generate a multi-stage Dockerfile for this Node.js API.
Stage 1 (builder): install all deps, build TypeScript.
Stage 2 (production): copy only dist + node_modules/prod,
run as non-root user 'node', expose port 3000, health check
GET /health every 30s. Use node:22-alpine throughout."
# Claude produces — production Node.js Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine AS production
WORKDIR /app
RUN addgroup -S appgroup && adduser -S node -G appgroup 2>/dev/null || true
COPY --from=builder --chown=node:node /app/dist ./dist
COPY --from=builder --chown=node:node /app/node_modules ./node_modules
COPY --from=builder --chown=node:node /app/package.json ./package.json
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
.dockerignore to every project. Without it, every COPY . . instruction sends your entire repo context to the daemon. Ask Claude: "Generate a .dockerignore for this Node.js + TypeScript project."docker-compose.yml for Local Development
# Prompt
"Generate a docker-compose.yml for local dev. Services:
app (build from .), postgres:16-alpine with named volume,
redis:7-alpine. App depends on postgres/redis being healthy.
Mount src/ for hot-reload. Expose app on 3000, postgres on 5432."
# Claude produces
services:
app:
build: .
ports: ["3000:3000"]
volumes:
- ./src:/app/src:ro
environment:
DATABASE_URL: postgres://dev:dev@postgres:5432/myapp
REDIS_URL: redis://redis:6379
depends_on:
postgres: {condition: service_healthy}
redis: {condition: service_healthy}
networks: [app_network]
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
volumes: [pg_data:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev -d myapp"]
interval: 5s
timeout: 5s
retries: 10
networks: [app_network]
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
networks: [app_network]
volumes:
pg_data:
networks:
app_network:
driver: bridge
Hooks for Docker Validation
Catch Dockerfile and compose errors immediately after every edit, not at deploy time.
# .claude/settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "if [ -f Dockerfile ]; then docker build --check . 2>&1 | head -30; fi"
},
{
"type": "command",
"command": "if [ -f docker-compose.yml ]; then docker compose config --quiet 2>&1 | head -20; fi"
}
]
}
]
}
}
docker build --check . (Docker Desktop 4.27+ / Engine 25+) validates Dockerfile syntax and best practices without building the image — fast feedback for every edit.Docker Workflow Prompt Patterns
| Goal | Prompt to Claude Code |
|---|---|
| Reduce image size | "Audit this Dockerfile. Identify unused layers, dev dependencies in production, and cache-busting COPY order. Suggest optimizations with expected size reduction." |
| Add Docker secrets | "Refactor the Dockerfile to use Docker BuildKit secrets for npm install instead of passing the NPM_TOKEN as a build arg." |
| Multi-arch build | "Add a GitHub Actions workflow that builds and pushes amd64 + arm64 images to Docker Hub using docker buildx bake." |
| Distroless base | "Migrate the production stage from node:22-alpine to gcr.io/distroless/nodejs22-debian12. Show what changes are needed." |
| Compose override | "Generate docker-compose.override.yml for local dev with volumes for hot-reload and DEBUG=true, leaving compose.yml unchanged for CI." |
| Kubernetes migration | "Convert this docker-compose.yml to Kubernetes Deployment + Service + ConfigMap manifests." |
Debugging Container Issues
Paste container output directly into Claude Code — it identifies the failure mode and proposes a fix in one shot.
CrashLoopBackOff / OOMKilled
# Run in terminal, paste output to Claude
docker logs --tail 50 $(docker ps -lq)
docker inspect $(docker ps -lq) | jq '.[0].State'
# Claude Code prompt
"Container exits with code 137 (OOMKilled). Here are the logs: [paste].
Recommend memory limit tuning and profiling approach."
Port Conflicts
# Diagnose bind errors
docker compose up 2>&1 | grep -E "bind|address already in use"
# Claude Code prompt
"Port 5432 bind failed. Identify which local process holds it
and suggest compose port mapping fix."
Volume Permission Errors
# Claude Code prompt
"Container writes fail with EACCES on /data. The volume is mounted
from host. Show how to fix ownership with initContainers or entrypoint chown."
Layer Caching Best Practices
| Pattern | Why it matters | Example |
|---|---|---|
| COPY package.json before source | Invalidates dep layer only when deps change, not on every source edit | COPY package*.json ./ && RUN npm ci |
| Pin base image digests in CI | Reproducible builds; SHA prevents silent upstream changes | FROM node:22-alpine@sha256:abc... |
| Combine RUN commands | Each RUN is a layer; combining reduces final image size | RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* |
| Use BuildKit cache mounts | Persists package manager caches across builds without layer bloat | RUN --mount=type=cache,target=/root/.npm npm ci |
| Multi-stage for test isolation | Test stage includes dev deps; production stage copies only built artifacts | Three stages: base → test → production |
Try the Claude Cost Calculator →
Python Service Dockerfile Pattern
# Claude Code prompt for Python services
"Generate a multi-stage Dockerfile for a FastAPI service:
builder installs dependencies with pip, production uses
python:3.12-slim, runs uvicorn on port 8000 as non-root,
includes healthcheck on /health. Use .venv pattern for isolation."
FROM python:3.12-slim AS builder
WORKDIR /app
RUN python -m venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim AS production
WORKDIR /app
RUN useradd -r -s /bin/false appuser
COPY --from=builder --chown=appuser /app/.venv /app/.venv
COPY --chown=appuser . .
ENV PATH="/app/.venv/bin:$PATH"
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
5 Docker Tips for Claude Code Users
- 1. Always generate .dockerignore first — ask Claude "Generate a .dockerignore for [language] project" before any Dockerfile work. Missing dockerignore = slow builds and leaked secrets.
- 2. Use CLAUDE.md to specify your registry — include the Docker Hub / ECR / GCR registry and tag format so Claude generates correct push commands without asking.
- 3. Paste `docker stats` output — when tuning resource limits, paste live stats into Claude Code and ask it to recommend CPU/memory limits for the compose file.
- 4. Request Trivy scans inline — ask Claude to add a Trivy vulnerability scan step to your CI Dockerfile so every image build gets a CVE report.
- 5. Version your compose files — ask Claude to extract environment-specific variables into .env.example and document every variable with a comment explaining valid values.
Related Guides
Kubernetes workflows → | Terraform & IaC → | CI/CD automation → | Python & FastAPI → | Hook patterns →