Docker image optimization patterns including multi-stage builds, layer caching, security hardening, and size reduction techniques. Use when building Docker images, optimizing container size, improving build performance, or implementing Docker security best practices. Reduces image sizes by 70-90% and build times by 50-80%.
Add this skill
npx mdskills install applied-artificial-intelligence/docker-optimizationComprehensive Docker optimization guide with concrete examples showing 70-90% size reductions
1---2name: docker-optimization3description: Docker image optimization patterns including multi-stage builds, layer caching, security hardening, and size reduction techniques. Use when building Docker images, optimizing container size, improving build performance, or implementing Docker security best practices. Reduces image sizes by 70-90% and build times by 50-80%.4---56# Docker Optimization Patterns78Comprehensive guide to optimizing Docker images for size, build speed, and security. Covers multi-stage builds, layer caching strategies, security hardening, and production deployment patterns.910---1112## Quick Reference1314**When to use this skill:**15- Building production Docker images16- Optimizing image size (reducing from 500MB+ to <100MB)17- Improving Docker build times18- Implementing Docker security best practices19- Debugging slow builds or large images20- Setting up Docker for microservices2122**Common triggers:**23- "My Docker image is too large"24- "Docker builds take forever"25- "How do I optimize this Dockerfile"26- "Docker security best practices"27- "Multi-stage build pattern"2829---3031## Part 1: Multi-Stage Builds3233### The Problem: Bloated Images3435**Typical single-stage Dockerfile** (800MB+ image):36```dockerfile37FROM python:3.1138WORKDIR /app39COPY . .40RUN pip install -r requirements.txt41CMD ["python", "app.py"]42```4344**Problems:**45- Includes build tools (gcc, make, etc.) - 300MB+46- Includes pip cache - 100MB+47- Includes source .git directory - 50MB+48- Includes test files and dev dependencies - 50MB+49- **Total: 800MB+ for simple Python app**5051### The Solution: Multi-Stage Pattern5253**Optimized multi-stage Dockerfile** (120MB image):54```dockerfile55# Stage 1: Builder56FROM python:3.11-slim AS builder57WORKDIR /app5859# Install build dependencies in separate layer60RUN apt-get update && apt-get install -y --no-install-recommends \61 gcc \62 && rm -rf /var/lib/apt/lists/*6364# Copy only requirements first (cache optimization)65COPY requirements.txt .66RUN pip install --user --no-cache-dir -r requirements.txt6768# Stage 2: Runtime69FROM python:3.11-slim70WORKDIR /app7172# Copy only Python packages from builder73COPY --from=builder /root/.local /root/.local7475# Copy only application code76COPY app.py .77COPY src/ ./src/7879# Make sure scripts in .local are usable80ENV PATH=/root/.local/bin:$PATH8182# Run as non-root user83RUN useradd -m appuser && chown -R appuser:appuser /app84USER appuser8586CMD ["python", "app.py"]87```8889**Result: 800MB → 120MB (85% reduction)**9091### Multi-Stage Pattern Breakdown9293**Stage 1: Builder** (Throw away after build)94- Install build tools95- Compile dependencies96- Run tests (optional)97- Generate artifacts9899**Stage 2: Runtime** (Final image)100- Minimal base image101- Copy only artifacts from builder102- No build tools103- No source files (only compiled/necessary files)104105---106107## Part 2: Layer Caching Optimization108109### Understanding Docker Layer Caching110111Each instruction creates a layer. Docker caches unchanged layers.112113**Bad Order** (cache invalidated on every code change):114```dockerfile115FROM python:3.11-slim116COPY . . # ❌ Copies everything117RUN pip install -r requirements.txt # ❌ Runs on every code change118```119120**Good Order** (cache preserved):121```dockerfile122FROM python:3.11-slim123COPY requirements.txt . # ✅ Only requirements124RUN pip install -r requirements.txt # ✅ Cached if requirements unchanged125COPY . . # ✅ Code changes don't invalidate pip cache126```127128### Layer Caching Best Practices129130**1. Order by change frequency** (least to most):131```dockerfile132# 1. System dependencies (rarely change)133RUN apt-get update && apt-get install -y curl134135# 2. Language runtime (rarely changes)136FROM python:3.11-slim137138# 3. Dependencies (change occasionally)139COPY requirements.txt .140RUN pip install -r requirements.txt141142# 4. Application code (changes frequently)143COPY . .144```145146**2. Separate COPY operations**:147```dockerfile148# ❌ Bad: Invalidates cache on any file change149COPY . .150151# ✅ Good: Cache preserved unless specific files change152COPY package.json package-lock.json ./153RUN npm ci154COPY src/ ./src/155COPY public/ ./public/156```157158**3. Use .dockerignore**:159```160# .dockerignore161.git162.gitignore163node_modules164npm-debug.log165Dockerfile166.dockerignore167.env168.venv169__pycache__170*.pyc171tests/172docs/173```174175---176177## Part 3: Image Size Optimization178179### Choose Minimal Base Images180181**Image Size Comparison**:182```183python:3.11 → 1.01GB184python:3.11-slim → 130MB (87% smaller)185python:3.11-alpine → 50MB (95% smaller)186```187188**When to use each**:189- **Full image** (`python:3.11`): Never for production190- **Slim** (`python:3.11-slim`): Default choice, good compatibility191- **Alpine** (`python:3.11-alpine`): Smallest, but can have glibc issues192193### Multi-Stage Size Optimization194195**Node.js Example** (900MB → 150MB):196```dockerfile197# Builder stage198FROM node:20 AS builder199WORKDIR /app200COPY package*.json ./201RUN npm ci --only=production202203# Production stage204FROM node:20-alpine205WORKDIR /app206COPY --from=builder /app/node_modules ./node_modules207COPY . .208EXPOSE 3000209CMD ["node", "server.js"]210```211212**Result**: 900MB → 150MB (83% reduction)213214### Clean Up in Same Layer215216**❌ Bad** (creates large intermediate layers):217```dockerfile218RUN apt-get update219RUN apt-get install -y build-essential220RUN rm -rf /var/lib/apt/lists/*221```222223**✅ Good** (single layer, no intermediate garbage):224```dockerfile225RUN apt-get update && \226 apt-get install -y --no-install-recommends build-essential && \227 rm -rf /var/lib/apt/lists/*228```229230### Remove Build Dependencies After Use231232```dockerfile233RUN apt-get update && \234 apt-get install -y --no-install-recommends \235 gcc \236 g++ \237 make \238 && pip install --no-cache-dir -r requirements.txt \239 && apt-get purge -y --auto-remove \240 gcc \241 g++ \242 make \243 && rm -rf /var/lib/apt/lists/*244```245246---247248## Part 4: Security Best Practices249250### Don't Run as Root251252**❌ Bad** (runs as root):253```dockerfile254FROM python:3.11-slim255COPY app.py .256CMD ["python", "app.py"]257```258259**✅ Good** (runs as non-root user):260```dockerfile261FROM python:3.11-slim262263# Create non-root user264RUN useradd -m -u 1000 appuser && \265 mkdir -p /app && \266 chown -R appuser:appuser /app267268WORKDIR /app269USER appuser270271COPY --chown=appuser:appuser app.py .272CMD ["python", "app.py"]273```274275### Never Include Secrets in Image276277**❌ Bad** (secrets baked into image):278```dockerfile279ENV DATABASE_PASSWORD=secret123280COPY .env .281```282283**✅ Good** (secrets provided at runtime):284```dockerfile285# Pass secrets via environment variables at runtime286# docker run -e DATABASE_PASSWORD=$DB_PASS myapp287```288289**✅ Also Good** (Docker secrets):290```dockerfile291# Use Docker secrets (Swarm/Kubernetes)292CMD ["sh", "-c", "python app.py"]293# Secrets mounted at /run/secrets/294```295296### Scan Images for Vulnerabilities297298```bash299# Using Docker Scout300docker scout cves myapp:latest301302# Using Trivy303trivy image myapp:latest304305# Using Snyk306snyk container test myapp:latest307```308309### Use Specific Image Tags310311**❌ Bad** (unpredictable):312```dockerfile313FROM python:latest314```315316**✅ Good** (reproducible):317```dockerfile318FROM python:3.11.9-slim-bookworm319```320321---322323## Part 5: Build Performance Optimization324325### BuildKit (Modern Docker Builder)326327Enable BuildKit for faster builds:328```bash329export DOCKER_BUILDKIT=1330docker build -t myapp .331```332333**Benefits**:334- Parallel layer building335- Skip unused stages336- Better caching337- 30-50% faster builds338339### Build Cache Mounts340341**With BuildKit** (cache pip downloads):342```dockerfile343RUN --mount=type=cache,target=/root/.cache/pip \344 pip install -r requirements.txt345```346347**Benefits**:348- Pip packages cached between builds349- No need to clear cache (doesn't bloat image)350- Significantly faster rebuilds351352### Parallel Multi-Stage Builds353354BuildKit automatically parallelizes independent stages:355```dockerfile356# Stage 1: Frontend build (runs in parallel)357FROM node:20 AS frontend358WORKDIR /app/frontend359COPY frontend/package*.json ./360RUN npm ci361COPY frontend/ ./362RUN npm run build363364# Stage 2: Backend build (runs in parallel)365FROM python:3.11-slim AS backend366WORKDIR /app/backend367COPY backend/requirements.txt .368RUN pip install -r requirements.txt369370# Stage 3: Final image (waits for both stages)371FROM python:3.11-slim372COPY --from=frontend /app/frontend/dist /app/static373COPY --from=backend /app/backend /app374```375376---377378## Part 6: Production Patterns379380### Health Checks381382```dockerfile383FROM python:3.11-slim384COPY app.py .385386# Add health check387HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \388 CMD curl -f http://localhost:8000/health || exit 1389390CMD ["python", "app.py"]391```392393### Proper Signal Handling394395```dockerfile396# Use exec form to ensure proper signal handling397CMD ["python", "app.py"] # ✅ Receives SIGTERM398399# Not shell form400CMD python app.py # ❌ Shell doesn't forward signals401```402403### Labels for Metadata404405```dockerfile406LABEL org.opencontainers.image.title="MyApp"407LABEL org.opencontainers.image.version="1.2.3"408LABEL org.opencontainers.image.authors="team@example.com"409LABEL org.opencontainers.image.source="https://github.com/org/repo"410```411412---413414## Part 7: Language-Specific Patterns415416### Python Optimization417418```dockerfile419FROM python:3.11-slim AS builder420421# Prevent Python from writing pyc files422ENV PYTHONDONTWRITEBYTECODE=1423# Prevent Python from buffering stdout/stderr424ENV PYTHONUNBUFFERED=1425426# Install system dependencies427RUN apt-get update && apt-get install -y --no-install-recommends \428 gcc \429 && rm -rf /var/lib/apt/lists/*430431WORKDIR /app432COPY requirements.txt .433434# Install to user site-packages435RUN pip install --user --no-cache-dir -r requirements.txt436437# Runtime stage438FROM python:3.11-slim439ENV PYTHONDONTWRITEBYTECODE=1 \440 PYTHONUNBUFFERED=1 \441 PATH=/root/.local/bin:$PATH442443WORKDIR /app444COPY --from=builder /root/.local /root/.local445COPY . .446447RUN useradd -m appuser && chown -R appuser:appuser /app448USER appuser449450CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0"]451```452453### Node.js Optimization454455```dockerfile456FROM node:20-alpine AS builder457458WORKDIR /app459COPY package*.json ./460461# Install production dependencies only462RUN npm ci --only=production && \463 # Remove npm cache464 npm cache clean --force465466# Runtime stage467FROM node:20-alpine468469# Create non-root user470RUN addgroup -g 1001 -S nodejs && \471 adduser -S nodeuser -u 1001472473WORKDIR /app474COPY --from=builder --chown=nodeuser:nodejs /app/node_modules ./node_modules475COPY --chown=nodeuser:nodejs . .476477USER nodeuser478EXPOSE 3000479480CMD ["node", "server.js"]481```482483### Go Optimization484485```dockerfile486# Builder stage487FROM golang:1.21-alpine AS builder488489WORKDIR /app490COPY go.mod go.sum ./491RUN go mod download492493COPY . .494RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .495496# Runtime stage - minimal scratch image497FROM scratch498499# Copy CA certificates for HTTPS500COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/501502# Copy binary503COPY --from=builder /app/main /main504505EXPOSE 8080506ENTRYPOINT ["/main"]507```508509**Result**: 1GB → 15MB (98.5% reduction!)510511---512513## Part 8: Common Mistakes to Avoid514515### Mistake 1: Installing Recommended Packages516517**❌ Bad** (installs hundreds of unnecessary packages):518```dockerfile519RUN apt-get install curl520```521522**✅ Good** (minimal installation):523```dockerfile524RUN apt-get install -y --no-install-recommends curl && \525 rm -rf /var/lib/apt/lists/*526```527528### Mistake 2: Using ADD Instead of COPY529530**❌ Bad** (ADD has implicit behavior):531```dockerfile532ADD requirements.txt . # Can extract tarballs, fetch URLs533```534535**✅ Good** (COPY is explicit):536```dockerfile537COPY requirements.txt . # Only copies files538```539540### Mistake 3: Multiple FROM Without AS541542**❌ Bad** (can't reference previous stages):543```dockerfile544FROM python:3.11545RUN pip install -r requirements.txt546FROM python:3.11-slim547# Can't copy from previous stage!548```549550**✅ Good** (named stages):551```dockerfile552FROM python:3.11 AS builder553RUN pip install -r requirements.txt554FROM python:3.11-slim555COPY --from=builder /root/.local /root/.local556```557558### Mistake 4: Not Using .dockerignore559560Without .dockerignore:561- Copies .git directory (50MB+)562- Copies node_modules (100MB+)563- Copies test files564- Invalidates cache on any file change565566### Mistake 5: Hardcoding Versions Incorrectly567568**❌ Bad** (no control over patch versions):569```dockerfile570FROM python:3.11571```572573**✅ Good** (pin exact version):574```dockerfile575FROM python:3.11.9-slim-bookworm576```577578---579580## Part 9: Before/After Examples581582### Example 1: Python FastAPI App583584**Before** (1.2GB image, 5min build):585```dockerfile586FROM python:3.11587WORKDIR /app588COPY . .589RUN pip install -r requirements.txt590CMD ["uvicorn", "app:app"]591```592593**After** (140MB image, 2min build):594```dockerfile595FROM python:3.11-slim AS builder596WORKDIR /app597RUN apt-get update && apt-get install -y --no-install-recommends gcc && \598 rm -rf /var/lib/apt/lists/*599COPY requirements.txt .600RUN pip install --user --no-cache-dir -r requirements.txt601602FROM python:3.11-slim603ENV PATH=/root/.local/bin:$PATH604WORKDIR /app605COPY --from=builder /root/.local /root/.local606COPY app.py .607COPY src/ ./src/608609RUN useradd -m appuser && chown -R appuser:appuser /app610USER appuser611612CMD ["uvicorn", "app:app", "--host", "0.0.0.0"]613```614615**Results**:616- Size: 1.2GB → 140MB (88% reduction)617- Build time: 5min → 2min (60% faster)618- Security: Now runs as non-root619- Cache: Code changes don't rebuild dependencies620621---622623## Part 10: Quick Optimization Checklist624625**Image Size**:626- [ ] Use slim or alpine base images627- [ ] Multi-stage build (build tools in first stage only)628- [ ] Clean up in same layer (`apt-get install && rm -rf`)629- [ ] Use `--no-install-recommends` with apt-get630- [ ] Remove package manager cache (`pip --no-cache-dir`, `npm cache clean`)631- [ ] Use .dockerignore632633**Build Speed**:634- [ ] Order COPY by change frequency635- [ ] Copy dependency files before code636- [ ] Enable BuildKit637- [ ] Use build cache mounts638639**Security**:640- [ ] Run as non-root user641- [ ] Pin specific image versions642- [ ] Scan for vulnerabilities643- [ ] Never include secrets in image644- [ ] Use minimal base images645646**Production**:647- [ ] Add HEALTHCHECK648- [ ] Use exec form for CMD649- [ ] Add metadata labels650- [ ] Proper signal handling651- [ ] Set up proper logging652653---654655## Resources656657**Official Docker Documentation**:658- Multi-stage builds: https://docs.docker.com/build/building/multi-stage/659- Best practices: https://docs.docker.com/develop/dev-best-practices/660- BuildKit: https://docs.docker.com/build/buildkit/661662**Security Scanning**:663- Docker Scout: https://docs.docker.com/scout/664- Trivy: https://github.com/aquasecurity/trivy665- Snyk: https://snyk.io/product/container-vulnerability-management/666667**Base Images**:668- Docker Hub: https://hub.docker.com/669- Google Distroless: https://github.com/GoogleContainerTools/distroless670- Alpine Linux: https://alpinelinux.org/671
Full transparency — inspect the skill content before installing.