Dockerfile debugging is tedious. Layer caching, multi-stage builds, Alpine vs Debian, security scanning, Compose networking — each has its own footguns. Claude Code handles Docker and DevOps tasks well, especially the debugging and optimization work that requires deep knowledge of how the daemon processes layers.
This guide covers using Claude Code for container work: writing production Dockerfiles, debugging build failures, multi-service Compose stacks, and integrating containers into CI/CD.
Dockerfiles: Writing Production-Grade Ones
The difference between a working Dockerfile and a good Dockerfile is significant — image size, build time, security, and layer cache efficiency. Claude Code produces good ones when you give it the right constraints.
Write a production Dockerfile for a Node.js 22 app.
Requirements:
- Multi-stage build (build stage + runtime stage)
- Final image as small as possible
- Non-root user
- Proper layer caching (dependencies install before code copy)
- Health check
- The app runs with: npm start (listens on PORT env var, default 3000)
Claude generates:
# Build stage
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Runtime stage
FROM node:22-alpine AS runtime
WORKDIR /app
# Non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodeuser -u 1001 -G nodejs
# Copy built assets with correct ownership
COPY --from=builder --chown=nodeuser:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodeuser:nodejs /app/dist ./dist
COPY --from=builder --chown=nodeuser:nodejs /app/package.json ./
USER nodeuser
ENV PORT=3000
EXPOSE $PORT
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:$PORT/health || exit 1
CMD ["npm", "start"]
Key things Claude includes without being asked: --only=production to skip devDependencies in the final image, non-root user creation with proper GID/UID, --chown flag on COPY for secure file ownership, and health check with appropriate timing.
Debugging Docker Build Failures
Docker build errors can be cryptic. Show Claude the full error:
My Docker build is failing with:
"npm ERR! code EACCES
npm ERR! syscall mkdir
npm ERR! path /home/node/.npm"
Here's my Dockerfile: [paste]
Claude identifies that the error is a permissions issue — the npm cache directory isn’t writable by the non-root user. It adds the fix: ENV NPM_CONFIG_CACHE=/tmp/npm-cache before the npm commands, or creates the cache directory with proper permissions.
Layer Cache Invalidation
My Docker builds take 8 minutes every time even though I only changed one
source file. Here's my Dockerfile: [paste]
Claude spots the cache-busting pattern — usually COPY . . before RUN npm install, which means any file change invalidates the npm install cache. It reorders to: COPY package.json, install dependencies, then COPY source code.
Alpine vs Debian Issues
My build works with node:22 but fails with node:22-alpine.
Error: gyp ERR! find Python Python is not set
Claude explains that Alpine uses musl libc, which breaks native modules that depend on Python for compilation or expect glibc. It offers solutions in order of preference: use a pre-compiled version of the package, use a Debian-slim base image (node:22-slim), or install build tools in Alpine (apk add python3 make g++).
Docker Compose for Development
Claude Code writes complete Compose stacks that actually work:
Create a docker-compose.yml for local development.
Services: Next.js app, PostgreSQL 16, Redis 7, and Mailhog for email testing.
The app should hot-reload on code changes.
Postgres and Redis data should persist between restarts.
The app needs these env vars: DATABASE_URL, REDIS_URL, SMTP_HOST.
# Generated by Claude — connects all services correctly
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
- /app/node_modules # Anonymous volume prevents host node_modules override
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp_dev
REDIS_URL: redis://redis:6379
SMTP_HOST: mailhog
SMTP_PORT: 1025
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp_dev
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
ports:
- "5432:5432"
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
ports:
- "6379:6379"
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
volumes:
postgres_data:
redis_data:
Claude includes the depends_on condition check (so the app waits for Postgres to actually be ready, not just started) and the anonymous volume trick for node_modules (prevents the host’s node_modules from overriding the container’s).
Compose Networking
My frontend (port 3000) can't connect to my API (port 8000) in Docker Compose.
Both services are running but the frontend gets "connection refused."
Here's my Compose file: [paste]
Claude diagnoses Compose networking: services on the same Compose network communicate via service name, not localhost. The fix: change http://localhost:8000 to http://api:8000 (using the service name). It explains the Docker DNS resolution so you understand the pattern, not just the fix.
Multi-Stage Builds for Different Environments
I need three stages: dev (with hot reload), test (runs test suite),
and prod (optimized, no dev tools).
All three should share the same base and dependency install.
Claude generates a properly layered multi-stage Dockerfile:
FROM node:22-alpine AS base
WORKDIR /app
COPY package*.json ./
FROM base AS deps
RUN npm ci
FROM deps AS development
COPY . .
CMD ["npm", "run", "dev"]
FROM deps AS test
COPY . .
RUN npm test
FROM deps AS builder
COPY . .
RUN npm run build
FROM node:22-alpine AS production
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
USER appuser
CMD ["node", "dist/main.js"]
The FROM deps AS ... pattern lets each stage inherit the installed dependencies without reinstalling them.
Integrating with CI/CD
For Docker and GitHub Actions together, see the CI/CD guide. The key pattern Claude generates:
Add a GitHub Actions workflow that:
1. Builds the Docker image on every push to main
2. Runs the test stage
3. If tests pass, pushes to GitHub Container Registry
4. Tags with: latest, and the git SHA
Claude generates the full workflow with proper Docker layer caching (using cache-from/cache-to with GitHub Actions cache), GHCR authentication, and correct tagging strategy.
Security: Scanning and Hardening
Audit this Dockerfile for security issues.
[paste Dockerfile]
Claude reviews for common issues:
- Running as root (add non-root user)
COPY . .including sensitive files (add.dockerignore)- Outdated base image (specific tag, not
latest) - Exposed unnecessary ports
- Secrets baked into image (should be runtime env vars, never build args for secrets)
- Missing
--no-cacheon package manager installs
For actively maintained production images, Claude also suggests adding Trivy or Grype scanning to the CI pipeline.
Docker + Claude Code: Common Patterns
.dockerignore Generation
Generate a .dockerignore for a Node.js project.
Include: development files, test artifacts, secrets, large binaries.
Claude generates a comprehensive .dockerignore — node_modules, .env*, *.test.*, .git, coverage/, dist/ (from build stage perspective), IDE config files.
Kubernetes Manifests
Convert this Docker Compose service to a Kubernetes Deployment + Service.
The app needs: 3 replicas, resource limits, readiness and liveness probes,
and the database URL from a Secret.
Claude writes the Deployment with proper resources.requests/resources.limits, readiness and liveness probes matching the health check from the Dockerfile, and envFrom.secretRef for the database URL. It generates the Service as ClusterIP unless you specify LoadBalancer or NodePort.
Working with Claude Code and Docker Day-to-Day
The most effective pattern: have your CLAUDE.md include your containerization standards.
## Containerization
- Base images: node:22-alpine, python:3.12-slim, golang:1.23-alpine
- Always multi-stage builds — final image should be minimal
- Non-root user required in all production images
- Health checks required on all services
- Secrets via environment variables only — never baked into images
- Local dev: Docker Compose in docker-compose.dev.yml
With this in CLAUDE.md, every Dockerfile Claude generates follows your standards without being asked. This is covered in the CLAUDE.md guide.
For automating container operations — like triggering a rebuild when files change — Claude Code hooks can watch for file changes and run docker compose up --build automatically.
The Automation Stack
Claude Code handles Docker tasks at a qualitatively higher level than inline autocomplete because it can:
- Read the existing Dockerfile and understand the build context
- Run
docker buildand see the actual error output - Read the source code to understand what the container needs
- Fix and rebuild in sequence
This is especially powerful for debugging complex multi-service stacks where the issue often spans multiple config files. If you’re doing serious DevOps work, combining Claude Code with DevOps-specific skills from Claude Skills 360 gives you a ready-made library of deployment, monitoring, and infrastructure patterns.
Start with the free tier to see how Claude Code handles your specific Docker and deployment workflows before committing.