Skip to content

Build Optimization ​

Optimizing Docker builds reduces build time, image size, and resource consumption. This guide covers practical techniques for faster, smaller, and more efficient builds.

Build Time Optimization ​

1. Optimize Layer Ordering ​

Place instructions that change rarely at the top and frequently changing instructions at the bottom:

dockerfile
# ✅ Optimized: Dependencies change less often than source code
FROM node:20-alpine

WORKDIR /app

# 1. System deps (rarely change)
RUN apk add --no-cache curl

# 2. Copy dependency manifests (change occasionally)
COPY package.json package-lock.json ./

# 3. Install deps (cached when manifests unchanged)
RUN npm ci --only=production

# 4. Copy source (changes frequently)
COPY . .

# 5. Build
RUN npm run build

CMD ["node", "dist/server.js"]

2. Use Cache Mounts ​

Cache mounts persist package manager caches between builds:

dockerfile
# syntax=docker/dockerfile:1

FROM node:20-alpine
WORKDIR /app

COPY package.json package-lock.json ./

# npm cache persists between builds — 2-5x faster installs
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

COPY . .
CMD ["node", "server.js"]

Cache Mount Speedup by Language ​

LanguagePackage ManagerCache TargetTypical Speedup
Node.jsnpm/root/.npm2-5x
Node.jsyarn/usr/local/share/.cache/yarn2-5x
Node.jspnpm/root/.local/share/pnpm/store3-6x
Pythonpip/root/.cache/pip2-4x
Gomodules/go/pkg/mod3-5x
Rustcargo/usr/local/cargo/registry5-10x
JavaMaven/root/.m2/repository3-5x
JavaGradle/root/.gradle/caches3-5x
Rubybundler/usr/local/bundle/cache2-4x

3. Use Multi-stage Builds ​

Separate build tools from the runtime:

dockerfile
# Build stage: includes all build tools
FROM node:20 AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage: minimal runtime
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

4. Leverage Parallel Stage Execution ​

BuildKit executes independent stages concurrently:

dockerfile
# These three stages build in parallel
FROM node:20-alpine AS frontend
WORKDIR /app
COPY frontend/ .
RUN npm ci && npm run build

FROM golang:1.22-alpine AS api
WORKDIR /app
COPY api/ .
RUN go build -o server .

FROM python:3.12-slim AS worker
WORKDIR /app
COPY worker/ .
RUN pip install -r requirements.txt

# Final stage combines all
FROM alpine:3.19
COPY --from=frontend /app/dist /srv/public
COPY --from=api /app/server /srv/api
COPY --from=worker /app /srv/worker

5. Minimize Build Context ​

Use .dockerignore to reduce the files sent to the builder:

# .dockerignore
.git
node_modules
dist
build
*.md
tests/
coverage/
.env*
docker-compose*.yml
bash
# Check build context size
du -sh --exclude=.git .

# A large context significantly slows builds
# Target: < 10 MB for most projects

Image Size Optimization ​

1. Choose Minimal Base Images ​

Base ImageSizeUse Case
scratch0 MBStatic binaries (Go, Rust)
alpine:3.19~7 MBGeneral purpose minimal
distroless/static~2 MBStatic binaries with CA certs
distroless/base~20 MBDynamic binaries (C/C++)
distroless/nodejs20~130 MBNode.js apps
node:20-alpine~130 MBNode.js with shell
node:20-slim~200 MBNode.js Debian slim
node:20~1 GBFull Node.js Debian

2. Combine and Clean in Single Layer ​

dockerfile
# ❌ Bad: Multiple layers, cache not cleaned
RUN apt-get update
RUN apt-get install -y curl git
RUN apt-get clean

# ✅ Good: Single layer with cleanup
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        git && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

3. Remove Unnecessary Files ​

dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt && \
    find /usr/local/lib/python3.12 -type d -name __pycache__ -exec rm -rf {} + && \
    find /usr/local/lib/python3.12 -type f -name '*.pyc' -delete && \
    find /usr/local/lib/python3.12 -type f -name '*.pyo' -delete

COPY . .
CMD ["python", "app.py"]

4. Use Static Binaries with Scratch ​

dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o server .

FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

Result: Final image is typically 3-10 MB.

5. Production Dependencies Only ​

dockerfile
# Node.js: Install only production dependencies
RUN npm ci --only=production

# Python: Exclude dev dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Go: Build with optimizations
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o app .

# Rust: Release build
RUN cargo build --release

Build Performance Monitoring ​

Analyze Build Time ​

bash
# Show detailed build timing
docker build --progress=plain -t my-app:1.0 . 2>&1 | tee build.log

# Use BuildKit's built-in profiling
BUILDKIT_PROGRESS=plain docker build -t my-app:1.0 .

Analyze Image Size ​

bash
# View image size
docker images my-app

# View layer sizes
docker history my-app:1.0

# Detailed layer analysis with dive
dive my-app:1.0

# Check for wasted space
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  wagoodman/dive my-app:1.0

Image Size Analysis Tools ​

ToolDescriptionUsage
docker historyBuilt-in layer analysisdocker history my-app:1.0
diveInteractive layer explorerdive my-app:1.0
docker scoutVulnerability + size analysisdocker scout quickview my-app:1.0
slimAutomatic image minificationslim build my-app:1.0

Dockerfile Best Practices Checklist ​

OptimizationCategoryImpact
Use .dockerignoreBuild time⭐⭐⭐
Order layers by change frequencyBuild time⭐⭐⭐
Use cache mountsBuild time⭐⭐⭐
Use multi-stage buildsImage size⭐⭐⭐
Use minimal base imagesImage size⭐⭐⭐
Combine RUN commandsImage size⭐⭐
Clean package manager cachesImage size⭐⭐
Use --no-install-recommendsImage size⭐⭐
Pin dependency versionsReliability⭐⭐
Production dependencies onlyImage size⭐⭐
Parallel build stagesBuild time⭐⭐
External build cacheBuild time⭐⭐

Complete Optimized Example ​

dockerfile
# syntax=docker/dockerfile:1

# ============================================
# Stage 1: Dependencies
# ============================================
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

# ============================================
# Stage 2: Build
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build

# ============================================
# Stage 3: Production
# ============================================
FROM node:20-alpine

LABEL org.opencontainers.image.title="My Optimized App"
LABEL org.opencontainers.image.version="1.0.0"

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

COPY --from=deps --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --chown=appuser:appgroup package.json ./

USER appuser

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD wget -q --spider http://localhost:3000/health || exit 1

EXPOSE 3000
CMD ["node", "dist/server.js"]

Next Steps ​

基于 MIT 许可发布