Install
openclaw skills install @michaelatamuk/docker-optimizerOptimize Dockerfiles with multi-stage builds, layer caching, security best practices, and size reduction techniques
openclaw skills install @michaelatamuk/docker-optimizerAutomatically 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.
This skill analyzes your Dockerfile and provides optimizations for:
Supports multiple languages and ecosystems:
Optimized Dockerfiles build significantly faster:
Real example (Node.js app):
Smaller images mean faster deployments and lower costs:
Size comparison:
Optimized images have fewer vulnerabilities:
Security improvements:
Smaller, faster images reduce costs:
Cost example (1000 deployments/month):
Use this skill to optimize Docker images:
Build Performance:
Image Size:
Security:
Best Practices:
Your Dockerfile:
FROM node:latest
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
Ask Claude:
"Optimize this Dockerfile for production"
Analysis Report:
🔴 Critical Issues Found:
Using :latest tag (Line 1)
No multi-stage build (Overall)
Inefficient layer caching (Lines 5-7)
Running as root (No USER instruction)
🟡 Optimization Opportunities:
Large base image (node:latest = 900MB)
Missing .dockerignore
Optimized Dockerfile:
# Multi-stage build for minimal final image
FROM node:20-alpine AS builder
# Install 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 --force
# Copy application code
COPY . .
# Build step if needed (e.g., TypeScript, webpack)
# RUN npm run build
# Production stage
FROM node:20-alpine
# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /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 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)})"
# 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:
Your Dockerfile:
FROM python:3.11
WORKDIR /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 builder
# Install 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.txt
# Production stage
FROM python:3.11-slim
# Install 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 appuser
WORKDIR /app
# Copy installed packages from builder
COPY --from=builder /root/.local /home/appuser/.local
# Copy application code
COPY --chown=appuser:appuser . .
# Switch to non-root user
USER appuser
# Add Python user packages to PATH
ENV PATH=/home/appuser/.local/bin:$PATH
# Expose port
EXPOSE 8000
# Health 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:
Your Dockerfile:
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o main .
CMD ["./main"]
Optimized Dockerfile (Scratch-based):
# Build stage
FROM golang:1.21-alpine AS builder
# Install build dependencies
RUN apk add --no-cache git ca-certificates tzdata
WORKDIR /app
# Copy go mod files first for caching
COPY go.mod go.sum ./
# Download dependencies
RUN go mod download && \
go mod verify
# Copy 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 scratch
# Copy 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/zoneinfo
# Copy the binary
COPY --from=builder /app/main /main
# Expose port
EXPOSE 8080
# Add 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:latest
RUN apk --no-cache add ca-certificates tzdata
# Create non-root user
RUN addgroup -g 1001 -S appuser && \
adduser -S appuser -u 1001 -G appuser
WORKDIR /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:
Your Dockerfile:
FROM openjdk:17
WORKDIR /app
COPY . .
RUN ./mvnw clean package
CMD java -jar target/app.jar
Optimized Dockerfile:
# Build stage
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
# Copy dependency files first for caching
COPY pom.xml .
COPY src ./src
# Build application
RUN mvn clean package -DskipTests && \
mv target/*.jar app.jar
# Production stage
FROM eclipse-temurin:17-jre-alpine
# Install dumb-init for signal handling
RUN apk add --no-cache dumb-init
# Create non-root user
RUN addgroup -g 1001 -S spring && \
adduser -S spring -u 1001 -G spring
WORKDIR /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 1
# JVM 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 extract
# Final image with layers
FROM eclipse-temurin:17-jre-alpine
RUN 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:
Your Dockerfile:
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]
Optimized Dockerfile (Next.js):
# Dependencies stage
FROM node:20-alpine AS deps
RUN 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 ci
# Builder stage
FROM node:20-alpine AS builder
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build application
RUN npm run build
# Production stage
FROM node:20-alpine AS runner
WORKDIR /app
# Don't run as root
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
# Copy built application
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
# Copy production dependencies
COPY --from=deps --chown=nextjs:nodejs /app/prod_node_modules ./node_modules
USER 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 builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
# Copy built static files
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Create 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.d
# Use non-root user
USER nginx
EXPOSE 8080
HEALTHCHECK --interval=30s CMD wget --quiet --tries=1 --spider http://localhost:8080 || exit 1
CMD ["nginx", "-g", "daemon off;"]
Improvements:
"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)"
"Optimize for Kubernetes deployment"
"Optimize for AWS Lambda/Fargate"
"Optimize for Docker Swarm"
"Optimize for local development"
"Optimize Node.js Dockerfile using pnpm"
"Optimize Python Dockerfile for ML workload"
"Optimize Go Dockerfile for statically linked binary"
Start with specific base images
❌ FROM node:latest
✅ FROM node:20.11-alpine
Use .dockerignore
node_modules
.git
*.log
.env
Order layers by change frequency
COPY package.json ./ # Changes rarely
RUN npm install # Cached if package.json same
COPY . . # Changes often
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/*
Use multi-stage builds
Run as non-root
RUN adduser -D appuser
USER appuser
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)Enable BuildKit for advanced features:
export DOCKER_BUILDKIT=1
RUN --mount=type=cache,target=/root/.npm \
npm install
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install
docker build --secret id=npmrc,src=$HOME/.npmrc .
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
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 1
# Or use language runtime
HEALTHCHECK CMD node -e "..." || exit 1
Cause: File permissions or port binding
Solution:
# Fix file permissions
COPY --chown=appuser:appuser . .
# Use port > 1024
EXPOSE 8080 # Not 80
Cause: Unnecessary files in image
Solution: Check layers
docker history image:tag
dive image:tag # Visual layer explorer
FROM golang:1.21 AS builder
# ... build ...
FROM gcr.io/distroless/static-debian11
COPY --from=builder /app/main /
ENTRYPOINT ["/main"]
Benefits:
FROM --platform=$BUILDPLATFORM golang:1.21 AS builder
ARG TARGETOS TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build
Build:
docker buildx build --platform linux/amd64,linux/arm64 .
docker build --squash -t image:tag .
Reduces layers but loses cache efficiency.
# Build optimized image
docker build -t app:optimized .
# Check size
docker images app:optimized
# Test functionality
docker run -p 8080:8080 app:optimized
# Security scan
docker scan app:optimized
# 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%
Before Optimization:
After Optimization:
Annual Impact:
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)