CtrlK
BlogDocsLog inGet started
Tessl Logo

pantheon-ai/dockerfile-toolkit

Complete dockerfile toolkit with generation and validation capabilities

94

Quality

94%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

optimization_guide.mdvalidator/references/

Dockerfile Optimization Guide

Comprehensive guide for optimizing Docker images for size, build time, and runtime performance.

Image Size Optimization

1. Choose Minimal Base Images

Size Comparison:

ubuntu:22.04          ~80 MB
alpine:3.21           ~5 MB
distroless/base       ~20 MB
scratch               ~0 MB (empty)

When to use each:

Alpine - General purpose minimal Linux

FROM alpine:3.21
RUN apk add --no-cache python3
  • ✅ Very small (5 MB)
  • ✅ Has package manager
  • ✅ Good for interpreted languages
  • ⚠️ Uses musl libc (compatibility issues with some C libraries)

Distroless - Production containers

FROM gcr.io/distroless/python3
COPY --from=builder /app /app
  • ✅ No shell, package manager (secure)
  • ✅ Minimal attack surface
  • ✅ Small size
  • ⚠️ Cannot exec into container for debugging
  • ⚠️ Must use multi-stage builds

Scratch - Static binaries only

FROM scratch
COPY --from=builder /app/binary /
  • ✅ Absolutely minimal
  • ✅ Perfect for Go, Rust static binaries
  • ⚠️ No OS utilities
  • ⚠️ No debug capabilities

2. Multi-Stage Builds

Problem: Build tools bloat production images

Single-stage (bloated):

FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o server
CMD ["./server"]

# Result: ~1 GB (includes Go toolchain)

Multi-stage (optimized):

# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o server

# Production stage
FROM alpine:3.21
COPY --from=builder /app/server /server
CMD ["/server"]

# Result: ~10 MB (100x smaller!)

3. Layer Optimization

Combine RUN commands:

# Bad - 4 layers, poor caching
RUN apt-get update
RUN apt-get install -y curl
RUN curl -O https://example.com/file
RUN rm -f file

# Good - 1 layer, cache cleaned
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && curl -O https://example.com/file \
    && rm -rf /var/lib/apt/lists/*

4. Package Manager Cache Cleanup

APT (Debian/Ubuntu):

RUN apt-get update && apt-get install -y --no-install-recommends \
    package1 \
    package2 \
    && rm -rf /var/lib/apt/lists/*
  • Saves ~100-200 MB per layer
  • Must be in same RUN command

APK (Alpine):

RUN apk add --no-cache package1 package2
  • Doesn't create cache at all
  • Or: apk add package && rm -rf /var/cache/apk/*

YUM/DNF (RHEL/Fedora):

RUN yum install -y package \
    && yum clean all \
    && rm -rf /var/cache/yum

Pip (Python):

RUN pip install --no-cache-dir package

NPM (Node.js):

RUN npm ci --only=production
# Or with cache mount:
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

5. Use .dockerignore

Problem: Entire project copied into image

.dockerignore contents:
.git/
node_modules/
*.log
.env
tests/
docs/
README.md

Impact:

  • Faster builds (smaller context)
  • Smaller images (fewer files)
  • Prevents accidental secret leaks

Build Time Optimization

1. Leverage Build Cache

Order matters - least to most frequently changing:

# 1. Base image (rarely changes)
FROM node:21-alpine

# 2. System dependencies (rarely change)
RUN apk add --no-cache curl

# 3. Application dependencies (change occasionally)
COPY package*.json ./
RUN npm ci

# 4. Application code (changes frequently)
COPY . .
RUN npm run build

Why this works:

  • Docker caches each layer
  • Layers rebuild when files change
  • Putting frequently-changing files last preserves cache for earlier layers

2. BuildKit Cache Mounts

Enable BuildKit:

export DOCKER_BUILDKIT=1

Use cache mounts:

# syntax=docker/dockerfile:1

# Python with pip cache
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

# Node.js with npm cache
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# Go with module cache
RUN --mount=type=cache,target=/go/pkg/mod \
    go build -o app

Benefits:

  • Persistent cache across builds
  • Dramatically faster dependency installation
  • Shared cache between projects

3. Parallel Multi-Stage Builds

# These stages run in parallel
FROM alpine AS fetch-1
RUN wget https://example.com/file1

FROM alpine AS fetch-2
RUN wget https://example.com/file2

# This stage waits for both
FROM alpine
COPY --from=fetch-1 /file1 .
COPY --from=fetch-2 /file2 .

Runtime Performance Optimization

1. Exec Form for CMD/ENTRYPOINT

# Bad - shell form (extra shell process)
CMD python app.py

# Good - exec form (direct execution)
CMD ["python", "app.py"]

Benefits:

  • Faster startup (no shell)
  • Proper signal handling (SIGTERM)
  • Lower memory usage

2. Health Checks

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
    CMD curl -f http://localhost:8080/health || exit 1

Benefits:

  • Container orchestrators can detect unhealthy containers
  • Automatic restarts
  • Better uptime

3. Resource Awareness

# Use all available CPUs
ENV GOMAXPROCS=0

# Or limit to specific count
ENV GOMAXPROCS=4

Language-Specific Optimizations

Node.js

FROM node:21-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:21-alpine
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER node
CMD ["node", "server.js"]

Tips:

  • Use npm ci instead of npm install
  • Install only production dependencies
  • Use Alpine variant (node:21-alpine vs node:21 = 150MB vs 900MB)

Python

FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.12-slim
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
COPY . .
USER nobody
CMD ["python", "app.py"]

Tips:

  • Use slim variant (python:3.12-slim vs python:3.12 = 50MB vs 1GB)
  • Install to --user to copy to final stage
  • Use --no-cache-dir to avoid pip cache

Go

FROM golang:1.21-alpine AS builder
WORKDIR /src
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app

FROM scratch
COPY --from=builder /app /app
ENTRYPOINT ["/app"]

Tips:

  • Use scratch for static binaries
  • Disable CGO for static linking
  • Use -ldflags="-s -w" to strip debug info (smaller binary)

Java

FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package

FROM eclipse-temurin:21-jre-alpine
COPY --from=builder /app/target/*.jar /app.jar
CMD ["java", "-jar", "/app.jar"]

Tips:

  • Use JRE instead of JDK for runtime (smaller)
  • Download dependencies separately for caching
  • Consider custom JRE with jlink for minimal image

Advanced Techniques

1. Multi-Architecture Builds

docker buildx build --platform linux/amd64,linux/arm64 -t myapp .

2. Build Secrets

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci
docker build --secret id=npmrc,src=$HOME/.npmrc .

Benefits:

  • Secrets not in final image
  • Not in build history
  • Secure credential usage

3. SSH Mounts

RUN --mount=type=ssh \
    git clone git@github.com:private/repo.git
docker build --ssh default .

4. Layer Squashing

docker build --squash -t myapp .

Benefits:

  • Single layer in final image
  • Smaller size if cleanup commands are separate

Drawbacks:

  • Loses layer caching benefits
  • Slower rebuilds

Optimization Checklist

  • Use minimal base image (Alpine, distroless, scratch)
  • Implement multi-stage builds
  • Combine RUN commands
  • Clean package manager cache
  • Order layers by change frequency
  • Use BuildKit cache mounts
  • Create .dockerignore file
  • Use exec form for CMD/ENTRYPOINT
  • Add HEALTHCHECK for services
  • Pin dependency versions
  • Remove development dependencies
  • Use --no-install-recommends for apt
  • Consider language-specific optimizations
  • Enable BuildKit features

Measuring Optimization

Before Optimization

docker images myapp
# REPOSITORY   TAG       SIZE
# myapp        latest    1.2GB

After Optimization

docker images myapp-optimized
# REPOSITORY        TAG       SIZE
# myapp-optimized   latest    50MB

Build Time Comparison

time docker build -t myapp .
# real    5m30s

time docker build -t myapp-optimized .
# real    0m45s (with cache)

Tools for Analysis

dive - Layer Analysis

dive myapp:latest
  • Shows layer-by-layer size
  • Identifies wasted space
  • Suggests optimizations

docker history

docker history myapp:latest
  • Shows each layer's size
  • Identifies large layers

docker scout

docker scout cves myapp:latest
  • Scans for vulnerabilities
  • Recommends base image updates

Resources

  • Docker Best Practices
  • BuildKit Documentation
  • Multi-Stage Builds
  • dive - Layer Explorer

tile.json