Analyze and optimize Docker images for size and build speed
Copy the command definition below into:
~/.claude/commands/docker-optimize.md---
name: docker-optimize
description: Analyze and optimize Docker images for size and build speed
shortcut: do
category: devops
difficulty: advanced
estimated_time: 3 minutes
---
<!-- DESIGN DECISION: Helps reduce bloated Docker images -->
<!-- Docker images often become bloated due to poor layer ordering, unnecessary files,
dev dependencies in production, etc. This command analyzes existing Dockerfiles
and provides specific optimization recommendations with metrics. -->
<!-- VALIDATION: Real-world results -->
<!-- Node.js app: 1.2GB → 150MB (87% reduction) -->
<!-- Python app: 900MB → 120MB (86% reduction) -->
<!-- Go app: 800MB → 12MB (98% reduction) -->
# Docker Image Optimizer
Analyzes existing Docker images and Dockerfiles, identifying size/speed bottlenecks and providing specific optimizations with before/after metrics.
## When to Use This
- Image is larger than expected (>500MB for simple app)
- Build times are slow (>5 minutes)
- Want to reduce infrastructure costs
- Deploying to bandwidth-limited environments
- Image already optimized (<200MB, multi-stage build)
## How It Works
You are a Docker optimization expert. When user runs `/docker-optimize` or `/do`:
1. **Analyze current state:**
```bash
# Read existing Dockerfile
# Check image layers with: docker history <image>
# Calculate current size with: docker images <image>
```
2. **Identify issues:**
- Using bloated base images (ubuntu vs alpine)
- Missing multi-stage builds
- Poor layer caching (dependencies installed after code copy)
- Dev dependencies in production
- Large build context (.dockerignore missing)
- Unnecessary files in image
- Inefficient RUN commands (multiple layers)
3. **Calculate potential savings:**
```
Current Image Size: 1,200 MB
Optimized Size: 150 MB
Reduction: 87% (1,050 MB saved)
Current Build Time: 5 min
Optimized Time: 1 min
Speedup: 80% faster
```
4. **Provide optimized Dockerfile:**
- Multi-stage build implementation
- Minimal base image selection
- Optimal layer ordering
- .dockerignore additions
- Combined RUN commands
- Non-root user
5. **Show side-by-side comparison:**
- Before vs After metrics
- Specific changes explained
- Validation commands
## Output Format
```markdown
## Current State Analysis
**Image:** [image-name:tag]
**Base Image:** [current-base]
**Total Size:** [X MB]
**Build Time:** [Y min]
**Layers:** [N layers]
## Issues Identified
1. **[Issue]** - Impact: [size/time impact]
2. **[Issue]** - Impact: [size/time impact]
3. **[Issue]** - Impact: [size/time impact]
## Optimization Plan
**Target:**
- Size: [X MB] (↓ [Y]% reduction)
- Build Time: [X min] (↓ [Y]% faster)
- Layers: [N layers] (optimized)
```
```dockerfile
# Optimized Dockerfile
[Complete optimized version with inline comments]
```
```
# Updated .dockerignore
[Additions to reduce build context]
```
```markdown
## Before vs After
| Metric | Before | After | Improvement |
|--------------|-----------|-----------|-------------|
| Image Size | [X MB] | [Y MB] | ↓ [Z]% |
| Build Time | [X min] | [Y min] | ↓ [Z]% |
| Layers | [N] | [M] | Optimized |
| Base Image | [old] | [new] | Smaller |
| Security | root ️ | non-root | Hardened |
## Key Optimizations
1. **[Optimization]** - Saves [X MB / Y sec]
2. **[Optimization]** - Saves [X MB / Y sec]
3. **[Optimization]** - Saves [X MB / Y sec]
```
## Examples
### Example 1: Bloated Node.js Image
**User:** `/do` (with existing Dockerfile)
**Current Dockerfile:**
```dockerfile
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "index.js"]
```
**Analysis:**
```markdown
## Current State Analysis
**Image:** my-app:latest
**Base Image:** node:20 (full Debian-based, ~900MB)
**Total Size:** 1,200 MB
**Build Time:** 3 min
**Layers:** 6 layers
## Issues Identified
1. **Bloated base image (node:20)** - Impact: +900MB
- Using full Debian when Alpine works
- Includes unnecessary build tools
2. **No multi-stage build** - Impact: +200MB
- Dev dependencies in production image
- npm cache included in final image
3. **Poor layer caching** - Impact: +2 min on code changes
- Copying all files before npm install
- Cache invalidated on any file change
4. **Missing .dockerignore** - Impact: +30 sec build time
- Copying node_modules, .git into build context
- Large unnecessary files slow down build
5. **Running as root** - Impact: Security vulnerability
- No non-root user configured
6. **No health check** - Impact: Production readiness
- Can't verify container health
## Optimization Plan
**Target:**
- Size: 150 MB (↓ 87% reduction, saves 1,050 MB)
- Build Time: 45 sec (↓ 75% faster, saves 2 min 15 sec)
- Layers: 8 layers (optimized for caching)
```
**Optimized Dockerfile:**
```dockerfile
# Multi-stage build for Node.js
# Before: 1,200 MB | After: 150 MB (87% reduction)
# Stage 1: Build environment
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files FIRST (better caching)
# Changed: This layer only rebuilds when dependencies change
COPY package*.json ./
# Install ALL dependencies (dev + production)
RUN npm ci
# Copy source code AFTER installing deps
COPY . .
# Build step (if TypeScript/webpack/etc)
# RUN npm run build
# Stage 2: Production environment
FROM node:20-alpine AS production
# Create non-root user (security hardening)
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install ONLY production dependencies
# Changed: Excludes dev deps (jest, eslint, etc)
RUN npm ci --only=production && \
npm cache clean --force
# Copy built artifacts from builder stage
COPY --from=builder --chown=nodejs:nodejs /app .
# Switch to non-root user
USER nodejs
# Expose port
EXPOSE 3000
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# Start application
CMD ["node", "index.js"]
```
**Updated .dockerignore:**
```
# Prevent unnecessary files in build context
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
README.md
.vscode
.idea
.DS_Store
dist
build
coverage
.nyc_output
*.md
```
**Before vs After:**
| Metric | Before | After | Improvement |
|--------------|----------------|----------------|--------------------|
| Image Size | 1,200 MB | 150 MB | ↓ 87% (-1,050 MB) |
| Build Time | 3 min | 45 sec | ↓ 75% (-2m 15s) |
| Layers | 6 | 8 | Optimized caching |
| Base Image | node:20 (900MB)| node:20-alpine (40MB)| -860 MB |
| Security | root ️ | nodejs user | Hardened |
| Health Check | None ️ | Configured | Production-ready |
**Key Optimizations:**
1. **Multi-stage build** - Saves 200MB
- Separates build and runtime environments
- Excludes dev dependencies from production
2. **Alpine base image** - Saves 860MB
- node:20 (900MB) → node:20-alpine (40MB)
- Minimal attack surface
3. **Improved layer caching** - Saves 2 min on rebuilds
- Dependencies installed before code copy
- Only rebuilds when package.json changes
4. **.dockerignore** - Saves 30 sec
- Excludes node_modules, .git from build context
- Faster context transfer to Docker daemon
5. **npm cache clean** - Saves 50MB
- Removes npm cache after install
- Reduces final image size
6. **Non-root user** - Security hardening
- Runs as nodejs:nodejs (UID 1001)
- Follows security best practices
### Example 2: Python Flask App
**User:** `/do`
**Current Dockerfile:**
```dockerfile
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
```
**Analysis:**
```markdown
## Current State Analysis
**Image:** flask-app:latest
**Base Image:** python:3.11 (full Debian-based, ~900MB)
**Total Size:** 950 MB
**Build Time:** 2 min
**Layers:** 5 layers
## Issues Identified
1. **Bloated base image** - Impact: +900MB
2. **No .dockerignore** - Impact: +20 sec
3. **Poor layer caching** - Impact: +1 min
4. **Running as root** - Impact: Security risk
5. **Pip cache in image** - Impact: +50MB
## Optimization Plan
**Target:**
- Size: 120 MB (↓ 87%, saves 830 MB)
- Build Time: 40 sec (↓ 67%, saves 1 min 20 sec)
```
**Optimized Dockerfile:**
```dockerfile
# Optimized Python Flask Dockerfile
# Before: 950 MB | After: 120 MB (87% reduction)
FROM python:3.11-slim AS production
# Prevent Python from buffering stdout/stderr
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# Create non-root user
RUN groupadd -g 1001 appuser && \
useradd -r -u 1001 -g appuser appuser
WORKDIR /app
# Copy requirements FIRST (better caching)
COPY requirements.txt .
# Install dependencies with no cache
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY --chown=appuser:appuser . .
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()"
# Start application
CMD ["python", "app.py"]
```
**Before vs After:**
| Metric | Before | After | Improvement |
|--------------|------------------|------------------|--------------------|
| Image Size | 950 MB | 120 MB | ↓ 87% (-830 MB) |
| Build Time | 2 min | 40 sec | ↓ 67% (-1m 20s) |
| Base Image | python:3.11 (900MB)| python:3.11-slim (130MB)| -770 MB |
## Pro Tips
**Multi-stage builds typically save 80-90% size**
**Alpine/slim base images reduce size dramatically**
**Proper layer ordering speeds up rebuilds 5-10x**
**Always use .dockerignore (excludes unnecessary files)**
**Use `--no-cache-dir` with pip/npm to reduce size**
## Validation Commands
After optimization, verify improvements:
```bash
# Build optimized image
docker build -t app:optimized .
# Compare sizes
docker images app:latest app:optimized
# Inspect layers
docker history app:optimized
# Measure build time
time docker build --no-cache -t app:optimized .
# Scan for vulnerabilities
docker scan app:optimized
# OR
trivy image app:optimized
# Test container
docker run -p 3000:3000 app:optimized
```
## Common Optimizations Checklist
- [ ] Use multi-stage build (if applicable)
- [ ] Switch to alpine/slim base image
- [ ] Install dependencies before copying code
- [ ] Create and populate .dockerignore
- [ ] Combine multiple RUN commands
- [ ] Remove package manager cache
- [ ] Run as non-root user
- [ ] Add health check
- [ ] Use specific version tags (not :latest)
- [ ] Scan for vulnerabilities
## Troubleshooting
**Issue: Alpine image causes errors**
→ Some apps need glibc (alpine uses musl libc)
→ Try -slim variant instead (e.g., python:3.11-slim)
**Issue: Build time didn't improve**
→ Verify layer caching is working
→ Check if dependencies change frequently
→ Use BuildKit for advanced caching
**Issue: Image still large after optimization**
→ Use `docker history <image>` to find large layers
→ Check for large static files (move to CDN/volumes)
→ Verify .dockerignore is comprehensive