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 许可发布