Skip to content

Image Building Best Practices

Building efficient, secure, and maintainable Docker images is crucial for production-grade containerized applications. This guide covers proven best practices for creating optimized Docker images.

Use Minimal Base Images

Choosing the right base image significantly impacts your image size, security posture, and performance.

Base Image Comparison

Base ImageSizePackagesUse Case
ubuntu:22.04~77 MBFull systemGeneral purpose, debugging
debian:12-slim~75 MBMinimal DebianProduction workloads
alpine:3.19~7 MBMinimal with muslSmallest footprint
distroless~2-20 MBNo shell, minimalMaximum security
scratch0 MBNothingStatically compiled binaries

Examples

dockerfile
# ❌ Bad: Using a full OS image
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y nodejs npm
COPY . /app
CMD ["node", "/app/server.js"]

# ✅ Good: Using a purpose-built slim image
FROM node:20-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]

Optimize Layer Caching

Docker caches each layer of your image. Order your Dockerfile instructions from least frequently changed to most frequently changed.

Layer Caching Strategy

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

WORKDIR /app

# Layer 1: Copy dependency definitions (changes rarely)
COPY package.json package-lock.json ./

# Layer 2: Install dependencies (cached if package files unchanged)
RUN npm ci --only=production

# Layer 3: Copy source code (changes frequently)
COPY src/ ./src/

CMD ["node", "src/index.js"]
dockerfile
# ❌ Bad: Copying everything first invalidates dependency cache
FROM node:20-alpine

WORKDIR /app

# Any change to source code invalidates all subsequent layers
COPY . .

RUN npm ci --only=production

CMD ["node", "src/index.js"]

Caching Order Principles

┌──────────────────────────────────┐
│   FROM (base image)              │  ← Changes rarely
├──────────────────────────────────┤
│   System dependencies (apt/apk) │  ← Changes rarely
├──────────────────────────────────┤
│   Package manifests (copy)       │  ← Changes occasionally
├──────────────────────────────────┤
│   Install dependencies (run)     │  ← Changes occasionally
├──────────────────────────────────┤
│   Application source (copy)      │  ← Changes frequently
├──────────────────────────────────┤
│   Build step (if applicable)     │  ← Changes frequently
├──────────────────────────────────┤
│   CMD / ENTRYPOINT               │  ← Changes rarely
└──────────────────────────────────┘

Minimize Image Size

Combine RUN Commands

Each RUN instruction creates a new layer. Combine related commands to reduce layers:

dockerfile
# ❌ Bad: Multiple RUN instructions create unnecessary layers
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN rm -rf /var/lib/apt/lists/*

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

Use .dockerignore

Prevent unnecessary files from being sent to the build context:

# .dockerignore
.git
.gitignore
node_modules
npm-debug.log
Dockerfile
docker-compose.yml
.env
*.md
tests/
docs/
.vscode/
.idea/
coverage/
.nyc_output/

Remove Unnecessary Files

dockerfile
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

# Install dependencies and clean up in one layer
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

COPY . .

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

Use Multi-stage Builds

Multi-stage builds dramatically reduce final image size by separating the build environment from the runtime environment:

dockerfile
# Stage 1: Build
FROM golang:1.22-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server .

# Stage 2: Runtime (minimal image)
FROM alpine:3.19

RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/server .

EXPOSE 8080
CMD ["./server"]

See the Multi-stage Builds guide for more details.

Set Proper User Permissions

Never run containers as root in production:

dockerfile
FROM node:20-alpine

# Create a non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

COPY --chown=appuser:appgroup package.json package-lock.json ./
RUN npm ci --only=production

COPY --chown=appuser:appgroup . .

# Switch to non-root user
USER appuser

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

Use HEALTHCHECK

Add health checks to enable Docker to monitor container health:

dockerfile
FROM nginx:1.25-alpine

COPY nginx.conf /etc/nginx/nginx.conf
COPY public/ /usr/share/nginx/html/

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

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

HEALTHCHECK Parameters

ParameterDefaultDescription
--interval30sTime between health checks
--timeout30sMaximum time for a check to complete
--start-period0sInitialization time before checks start
--retries3Consecutive failures needed to mark unhealthy

Use Labels for Metadata

dockerfile
FROM node:20-alpine

LABEL maintainer="[email protected]"
LABEL org.opencontainers.image.title="My Application"
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.description="A Node.js microservice"
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.licenses="MIT"

WORKDIR /app
COPY . .
RUN npm ci --only=production

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

Pin Dependency Versions

Always pin specific versions for reproducible builds:

dockerfile
# ✅ Good: Pinned versions
FROM node:20.11.0-alpine3.19

RUN apk add --no-cache \
    curl=8.5.0-r0 \
    tini=0.19.0-r2

# ❌ Bad: Unpinned versions
FROM node:latest

RUN apk add --no-cache curl tini

Complete Best Practices Example

dockerfile
# Build stage
FROM node:20.11.0-alpine3.19 AS builder

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

# Production stage
FROM node:20.11.0-alpine3.19

LABEL maintainer="[email protected]"
LABEL org.opencontainers.image.title="My App"

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

WORKDIR /app

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

RUN npm ci --only=production && npm cache clean --force

USER appuser

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

EXPOSE 3000

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

Image Size Optimization Checklist

PracticeImpactDifficulty
Use Alpine/slim base imagesHighLow
Multi-stage buildsHighMedium
Combine RUN commandsMediumLow
Use .dockerignoreMediumLow
Remove caches and temp filesMediumLow
Pin dependency versionsLow (size) / High (reliability)Low
Use --no-install-recommendsMediumLow
Use distroless imagesHighMedium

Next Steps

基于 MIT 许可发布