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="team@example.com"
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="team@example.com"
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 许可发布