Docs / Containers & Docker / Optimizing Docker Images for Production

Optimizing Docker Images for Production

By Admin · Feb 16, 2026 · Updated Apr 23, 2026 · 450 views · 2 min read

Why Image Size Matters

  • Faster deployments and scaling
  • Reduced bandwidth costs
  • Smaller attack surface
  • Faster container startup

Multi-Stage Builds

Separate build dependencies from runtime:

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Stage 2: Runtime
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

Result: Build tools, source code, and dev dependencies are excluded from the final image.

Layer Optimization

Order matters — least-changing first

# Bad: any code change invalidates npm install cache
COPY . .
RUN npm install

# Good: package.json changes rarely, so npm install is cached
COPY package*.json ./
RUN npm ci
COPY . .

Combine RUN commands

# Bad: 3 layers
RUN apt-get update
RUN apt-get install -y curl wget
RUN apt-get clean

# Good: 1 layer, smaller
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl wget && \
    rm -rf /var/lib/apt/lists/*

Base Image Selection

Base Image Size Best For
alpine ~5 MB Minimal, production
slim ~80 MB Debian-based, when alpine breaks
bookworm ~140 MB Full Debian, max compatibility
distroless ~20 MB Google's minimal, no shell
scratch 0 MB Static binaries (Go, Rust)

Go Example with Scratch

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

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

Final image: just your binary + CA certs. Often under 15 MB.

Security Best Practices

# Run as non-root
RUN adduser -D -u 1001 appuser
USER appuser

# Use specific tags, not :latest
FROM node:20.11-alpine

# Scan for vulnerabilities
# docker scout cves myimage:latest

.dockerignore

.git
node_modules
.env
*.md
docker-compose*.yml
.github
tests
coverage

Tip Run docker image history myimage:latest to see each layer's size. Target the largest layers for optimization.

Was this article helpful?