Docker Optimizer
by @michaelatamuk
Optimize Dockerfiles with multi-stage builds, layer caching, security best practices, and size reduction techniques
clawhub install docker-optimizerπ About This Skill
name: docker-optimizer description: Optimize Dockerfiles with multi-stage builds, layer caching, security best practices, and size reduction techniques version: 1.0.0 metadata: openclaw: emoji: "π³" homepage: https://docs.docker.com/build/building/best-practices/ requires: bins: - docker os: ["macos", "linux", "windows"]
Docker Optimizer
Automatically analyzes and optimizes your Dockerfiles for faster builds, smaller images, better caching, and enhanced security. Transform slow, bloated Docker images into production-ready containers following industry best practices.
What This Skill Does
This skill analyzes your Dockerfile and provides optimizations for:
Supports multiple languages and ecosystems:
Why Use This Skill
Dramatically Faster Builds
Optimized Dockerfiles build significantly faster:
Real example (Node.js app):
Massive Size Reduction
Smaller images mean faster deployments and lower costs:
Size comparison:
Enhanced Security
Optimized images have fewer vulnerabilities:
Security improvements:
Lower Cloud Costs
Smaller, faster images reduce costs:
Cost example (1000 deployments/month):
When to Use This Skill
Use this skill to optimize Docker images:
When NOT to Use This Skill
How It Works
Step-by-Step Analysis
1. Parses Dockerfile: Analyzes all instructions and stages 2. Identifies anti-patterns: Finds inefficiencies and security issues 3. Measures impact: Calculates potential size and speed improvements 4. Suggests optimizations: Provides specific, actionable changes 5. Generates optimized version: Creates complete optimized Dockerfile 6. Validates syntax: Ensures optimized Dockerfile is valid 7. Estimates improvements: Projects size reduction and build time savings
Optimization Categories
Build Performance:
Image Size:
Security:
Best Practices:
Usage Examples
Example 1: Node.js Application Optimization
Your Dockerfile:
FROM node:latestWORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
Ask Claude:
"Optimize this Dockerfile for production"
Analysis Report:
π΄ Critical Issues Found:
1. Using :latest tag (Line 1) - Problem: Unpredictable builds, breaks in production - Impact: Potential breaking changes, no reproducibility - Fix: Use specific version
2. No multi-stage build (Overall) - Problem: Includes dev dependencies (200MB+) - Impact: 3Γ larger image than necessary - Fix: Separate build and runtime stages
3. Inefficient layer caching (Lines 5-7) - Problem: Any file change invalidates npm install - Impact: 2-5 minute rebuild every time - Fix: Copy package files first
4. Running as root (No USER instruction) - Problem: Security vulnerability - Impact: Container compromise = root access - Fix: Add non-root user
π‘ Optimization Opportunities:
5. Large base image (node:latest = 900MB) - Recommendation: Use node:20-alpine (120MB) - Savings: 780MB (87% reduction)
6. Missing .dockerignore - Problem: Copying node_modules, .git, etc. - Impact: Slower builds, larger context - Fix: Create .dockerignore
Optimized Dockerfile:
# Multi-stage build for minimal final image
FROM node:20-alpine AS builderInstall build dependencies if needed
RUN apk add --no-cache python3 make g++
WORKDIR /app
Copy dependency files first for better caching
COPY package*.json ./Install dependencies
RUN npm ci --only=production && \
npm cache clean --forceCopy application code
COPY . .Build step if needed (e.g., TypeScript, webpack)
RUN npm run build
Production stage
FROM node:20-alpineInstall dumb-init for proper signal handling
RUN apk add --no-cache dumb-initCreate non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001WORKDIR /app
Copy only production dependencies and built app
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app .Switch to non-root user
USER nodejsExpose port
EXPOSE 3000Add 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)})"Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"]Start application
CMD ["node", "server.js"]
Recommended .dockerignore:
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
*.md
.vscode
.idea
coverage
.nyc_output
dist
build
Improvements:
Example 2: Python Flask Application
Your Dockerfile:
FROM python:3.11WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD python app.py
Ask Claude:
"Optimize this Python Dockerfile for production deployment"
Optimized Dockerfile:
# Build stage
FROM python:3.11-slim AS builderInstall build dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
gcc && \
rm -rf /var/lib/apt/lists/*WORKDIR /app
Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txtProduction stage
FROM python:3.11-slimInstall runtime dependencies only
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/*Create non-root user
RUN useradd -m -u 1000 appuserWORKDIR /app
Copy installed packages from builder
COPY --from=builder /root/.local /home/appuser/.localCopy application code
COPY --chown=appuser:appuser . .Switch to non-root user
USER appuserAdd Python user packages to PATH
ENV PATH=/home/appuser/.local/bin:$PATHExpose port
EXPOSE 8000Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()"Run with gunicorn for production
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "app:app"]
Improvements:
Example 3: Go Application (Maximum Optimization)
Your Dockerfile:
FROM golang:1.21WORKDIR /app
COPY . .
RUN go build -o main .
CMD ["./main"]
Optimized Dockerfile (Scratch-based):
# Build stage
FROM golang:1.21-alpine AS builderInstall build dependencies
RUN apk add --no-cache git ca-certificates tzdataWORKDIR /app
Copy go mod files first for caching
COPY go.mod go.sum ./Download dependencies
RUN go mod download && \
go mod verifyCopy source code
COPY . .Build binary with optimizations
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags='-w -s -extldflags "-static"' \
-a \
-o main .Final stage - minimal image
FROM scratchCopy CA certificates for HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/Copy timezone data
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfoCopy the binary
COPY --from=builder /app/main /mainExpose port
EXPOSE 8080Add health check (if health endpoint exists)
Note: scratch doesn't have shell, so health check needs external tool
or use alpine base if health checks are critical
Run binary
ENTRYPOINT ["/main"]
Alternative (Alpine-based for debugging):
# ... same builder ...Final stage with minimal Alpine
FROM alpine:latestRUN apk --no-cache add ca-certificates tzdata
Create non-root user
RUN addgroup -g 1001 -S appuser && \
adduser -S appuser -u 1001 -G appuserWORKDIR /app
Copy binary
COPY --from=builder /app/main .USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
ENTRYPOINT ["./main"]
Improvements:
Example 4: Java Spring Boot Application
Your Dockerfile:
FROM openjdk:17WORKDIR /app
COPY . .
RUN ./mvnw clean package
CMD java -jar target/app.jar
Optimized Dockerfile:
# Build stage
FROM maven:3.9-eclipse-temurin-17 AS builderWORKDIR /app
Copy dependency files first for caching
COPY pom.xml .
COPY src ./srcBuild application
RUN mvn clean package -DskipTests && \
mv target/*.jar app.jarProduction stage
FROM eclipse-temurin:17-jre-alpineInstall dumb-init for signal handling
RUN apk add --no-cache dumb-initCreate non-root user
RUN addgroup -g 1001 -S spring && \
adduser -S spring -u 1001 -G springWORKDIR /app
Copy JAR from builder
COPY --from=builder --chown=spring:spring /app/app.jar .USER spring
EXPOSE 8080
Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1JVM tuning for containers
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"ENTRYPOINT ["dumb-init", "--"]
CMD ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
Layered JAR approach (even better):
# Extract layers from JAR
FROM eclipse-temurin:17-jre-alpine AS builder
WORKDIR /app
COPY --from=build /app/app.jar .
RUN java -Djarmode=layertools -jar app.jar extractFinal image with layers
FROM eclipse-temurin:17-jre-alpineRUN apk add --no-cache dumb-init && \
addgroup -g 1001 -S spring && \
adduser -S spring -u 1001 -G spring
WORKDIR /app
Copy layers (better caching)
COPY --from=builder --chown=spring:spring /app/dependencies/ ./
COPY --from=builder --chown=spring:spring /app/spring-boot-loader/ ./
COPY --from=builder --chown=spring:spring /app/snapshot-dependencies/ ./
COPY --from=builder --chown=spring:spring /app/application/ ./USER spring
EXPOSE 8080
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
ENTRYPOINT ["dumb-init", "--"]
CMD ["sh", "-c", "java $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]
Improvements:
Example 5: Frontend Build (React/Next.js)
Your Dockerfile:
FROM node:18WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]
Optimized Dockerfile (Next.js):
# Dependencies stage
FROM node:20-alpine AS depsRUN apk add --no-cache libc6-compat
WORKDIR /app
Copy dependency files
COPY package.json package-lock.json ./Install dependencies
RUN npm ci --only=production && \
cp -R node_modules prod_node_modules && \
npm ciBuilder stage
FROM node:20-alpine AS builderWORKDIR /app
Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .Build application
RUN npm run buildProduction stage
FROM node:20-alpine AS runnerWORKDIR /app
Don't run as root
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001Copy built application
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/staticCopy production dependencies
COPY --from=deps --chown=nextjs:nodejs /app/prod_node_modules ./node_modulesUSER nextjs
EXPOSE 3000
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD node -e "require('http').get('http://localhost:3000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
CMD ["node", "server.js"]
Static site (Nginx):
# Build stage
FROM node:20-alpine AS builderWORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
Production stage
FROM nginx:alpineCopy built static files
COPY --from=builder /app/dist /usr/share/nginx/htmlCopy custom nginx config
COPY nginx.conf /etc/nginx/nginx.confCreate non-root user
RUN chown -R nginx:nginx /usr/share/nginx/html && \
chown -R nginx:nginx /var/cache/nginx && \
chown -R nginx:nginx /var/log/nginx && \
chown -R nginx:nginx /etc/nginx/conf.dUse non-root user
USER nginxEXPOSE 8080
HEALTHCHECK --interval=30s CMD wget --quiet --tries=1 --spider http://localhost:8080 || exit 1
CMD ["nginx", "-g", "daemon off;"]
Improvements:
Configuration
Specify Optimization Level
"Optimize for maximum size reduction (scratch/distroless)"
"Optimize for build speed (balanced approach)"
"Optimize for security (minimal attack surface)"
"Optimize for debugging (keep shell and tools)"
Target Environment
"Optimize for Kubernetes deployment"
"Optimize for AWS Lambda/Fargate"
"Optimize for Docker Swarm"
"Optimize for local development"
Language-Specific
"Optimize Node.js Dockerfile using pnpm"
"Optimize Python Dockerfile for ML workload"
"Optimize Go Dockerfile for statically linked binary"
Best Practices
For Best Results
1. Start with specific base images
β FROM node:latest
β
FROM node:20.11-alpine
2. Use .dockerignore
node_modules
.git
*.log
.env
3. Order layers by change frequency
COPY package.json ./ # Changes rarely
RUN npm install # Cached if package.json same
COPY . . # Changes often
4. Combine RUN commands
β RUN apt-get update
RUN apt-get install -y git
RUN apt-get clean β
RUN apt-get update && \
apt-get install -y git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
5. Use multi-stage builds - Build stage: Include all build tools - Production stage: Only runtime dependencies
6. Run as non-root
RUN adduser -D appuser
USER appuser
Base Image Selection Guide
Node.js:
node:20-alpine - Production (120MB)node:20-slim - If Alpine issues (200MB)node:20 - Development only (900MB)Python:
python:3.11-slim - Production (180MB)python:3.11-alpine - Smallest but compilation issues (50MB)python:3.11 - Development (900MB)Go:
scratch - Production static binary (0MB + app)alpine - Need shell/debugging (5MB + app)distroless/static - Google's minimal image (2MB + app)Java:
eclipse-temurin:17-jre-alpine - Production (170MB)eclipse-temurin:17-jre - If Alpine issues (270MB)eclipse-temurin:17 - Includes JDK (450MB)BuildKit Features
Enable BuildKit for advanced features:
export DOCKER_BUILDKIT=1
Cache Mounts
RUN --mount=type=cache,target=/root/.npm \
npm install
Secret Mounts
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install
Build Secrets
docker build --secret id=npmrc,src=$HOME/.npmrc .
Troubleshooting
Issue: Alpine breaks Python packages
Cause: Missing compiled dependencies
Solution: Use slim instead or install build deps
FROM python:3.11-alpine
RUN apk add --no-cache \
gcc musl-dev linux-headers \
postgresql-dev
Issue: Health checks failing
Cause: Missing tools in minimal image
Solution: Add minimal tools or use different check
# For Alpine
RUN apk add --no-cache curl
HEALTHCHECK CMD curl -f http://localhost/ || exit 1Or use language runtime
HEALTHCHECK CMD node -e "..." || exit 1
Issue: "Cannot run as non-root"
Cause: File permissions or port binding
Solution:
# Fix file permissions
COPY --chown=appuser:appuser . .Use port > 1024
EXPOSE 8080 # Not 80
Issue: Large image despite optimization
Cause: Unnecessary files in image
Solution: Check layers
docker history image:tag
dive image:tag # Visual layer explorer
Advanced Techniques
Distroless Images
FROM golang:1.21 AS builder
... build ...
FROM gcr.io/distroless/static-debian11
COPY --from=builder /app/main /
ENTRYPOINT ["/main"]
Benefits:
Multi-Architecture Builds
FROM --platform=$BUILDPLATFORM golang:1.21 AS builder
ARG TARGETOS TARGETARCHRUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build
Build:
docker buildx build --platform linux/amd64,linux/arm64 .
Layer Squashing
docker build --squash -t image:tag .
Reduces layers but loses cache efficiency.
Validation and Testing
Test optimized image
# Build optimized image
docker build -t app:optimized .Check size
docker images app:optimizedTest functionality
docker run -p 8080:8080 app:optimizedSecurity scan
docker scan app:optimized
Compare before/after
# Original
docker images app:original
app:original 1.2GB
Optimized
docker images app:optimized
app:optimized 180MB
Savings
echo "Reduced by $(( (1200-180)*100/1200 ))%"
Reduced by 85%
Related Resources
Real-World Impact
Case Study: E-Commerce Platform
Before Optimization:
After Optimization:
Annual Impact:
Case Study: Microservices (20 services)
Before:
After:
Impact:
Pro Tip: Start with multi-stage builds and Alpine/slim base images - these two changes typically provide 80% of the benefit with minimal effort. Then iterate on security and caching optimizations!
License: MIT-0 (Public Domain)
βοΈ Configuration
Specify Optimization Level
"Optimize for maximum size reduction (scratch/distroless)"
"Optimize for build speed (balanced approach)"
"Optimize for security (minimal attack surface)"
"Optimize for debugging (keep shell and tools)"
Target Environment
"Optimize for Kubernetes deployment"
"Optimize for AWS Lambda/Fargate"
"Optimize for Docker Swarm"
"Optimize for local development"
Language-Specific
"Optimize Node.js Dockerfile using pnpm"
"Optimize Python Dockerfile for ML workload"
"Optimize Go Dockerfile for statically linked binary"
π Tips & Best Practices
For Best Results
1. Start with specific base images
β FROM node:latest
β
FROM node:20.11-alpine
2. Use .dockerignore
node_modules
.git
*.log
.env
3. Order layers by change frequency
COPY package.json ./ # Changes rarely
RUN npm install # Cached if package.json same
COPY . . # Changes often
4. Combine RUN commands
β RUN apt-get update
RUN apt-get install -y git
RUN apt-get clean β
RUN apt-get update && \
apt-get install -y git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
5. Use multi-stage builds - Build stage: Include all build tools - Production stage: Only runtime dependencies
6. Run as non-root
RUN adduser -D appuser
USER appuser
Base Image Selection Guide
Node.js:
node:20-alpine - Production (120MB)node:20-slim - If Alpine issues (200MB)node:20 - Development only (900MB)Python:
python:3.11-slim - Production (180MB)python:3.11-alpine - Smallest but compilation issues (50MB)python:3.11 - Development (900MB)Go:
scratch - Production static binary (0MB + app)alpine - Need shell/debugging (5MB + app)distroless/static - Google's minimal image (2MB + app)Java:
eclipse-temurin:17-jre-alpine - Production (170MB)eclipse-temurin:17-jre - If Alpine issues (270MB)eclipse-temurin:17 - Includes JDK (450MB)