You've probably seen it before: a Docker image that's over 1GB, a container running as root, and a build cache that gets invalidated every single time. Claude Code generates production-quality Dockerfiles from CLAUDE.md rules — so you stop making the same mistakes.
The CLAUDE.md Rules That Drive the Dockerfile
Before writing a single line of Dockerfile, here's what lives in CLAUDE.md:
## Docker Rules
- No root user in production (create dedicated appuser)
- Use slim/alpine base images (node:20-slim preferred)
- No secrets hardcoded — use environment variables or Docker secrets
- .dockerignore must exclude: node_modules, .env, .env.*, .git
- Multi-stage build required:
- build stage: install devDependencies, compile TypeScript, generate Prisma client
- production stage: install prodDependencies only, copy build artifacts only
- COPY package.json first (before source) for layer cache efficiency
- HEALTHCHECK required on all long-running services
These rules are specific enough for Claude Code to generate compliant output on the first try.
The Dockerfile
# ---- Build Stage ----
FROM node:20-slim AS builder
WORKDIR /app
# Copy package files first for cache efficiency
COPY package*.json ./
RUN npm ci --ignore-scripts
# Copy source and compile
COPY tsconfig.json ./
COPY src ./src
COPY prisma ./prisma
RUN npx prisma generate
RUN npm run build
# ---- Production Stage ----
FROM node:20-slim AS production
WORKDIR /app
# Create non-root user
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid appgroup --shell /bin/bash --create-home appuser
# Install production dependencies only
COPY package*.json ./
RUN npm ci --omit=dev --ignore-scripts
# Copy compiled artifacts and Prisma client from builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
# Set ownership and switch to non-root
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD ["node", "dist/main.js"]
Two stages. The builder stage has everything needed to compile. The production stage has nothing it doesn't need.
The .dockerignore
node_modules
dist
.env
.env.*
.git
.gitignore
*.log
coverage
docs
*.md
Without this file, COPY . . copies your .env into the image. Every time.
Docker Compose for Development
version: '3.9'
services:
app:
build:
context: .
target: builder
volumes:
- ./src:/app/src
- ./prisma:/app/prisma
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://user:pass@postgres:5432/appdb
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: appdb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d appdb"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
target: builder means development uses the full build stage with devDependencies available.
GitHub Actions Cache Optimization
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
GitHub Actions caches Docker layers between runs. mode=max caches all intermediate layers, not just the final image.
What CLAUDE.md Gives You
The pattern: CLAUDE.md rules → Claude Code generates → Dockerfile that already follows them.
-
CLAUDE.mdwith Docker rules → multi-stage build on the first generation -
package.jsonCOPY before source → cache not invalidated on every source change -
HEALTHCHECK→ container orchestrators know when the service is actually ready - Non-root user → containers don't run as root in production
You don't have to add these after the fact. They're in the rules.
Want to see the full CLAUDE.md rules I use for Node.js backend projects? I've packaged them as a Code Review Pack on PromptWorks (¥980, /code-review). It includes Docker, logging, error handling, and security rules — the kind that catch real production issues before they get to code review.
What's the biggest Dockerfile mistake you've found in production? Curious what patterns people keep running into.
Top comments (0)