Optimizing Docker Images
Techniques for smaller, faster, more secure Docker images.
ContainersIntermediate
Why
Optimized images pull faster, use less disk, and have fewer vulnerabilities. Key techniques: .dockerignore, layer caching, distroless, and BuildKit.
When
Always optimize production images — faster deploys, lower costs, better security. Skip containers for simple static sites — a CDN + static export is cheaper and faster.
Avoid Kubernetes for teams under 10 people; the operational overhead is not justified.
How
1. .dockerignore — exclude node_modules, .git, tests
2. Layer order — copy package.json first, then source
3. Distroless — gcr.io/distroless/nodejs20, no shell
4. BuildKit — DOCKER_BUILDKIT=1 for parallel + cache
5. Slim base — alpine or debian-slim
6. Multi-arch — docker buildx for amd64 + arm64
7. Scan — docker scout or trivy for vulnerabilities
Example
# .dockerignore
node_modules
.git
*.test.ts
dist
.env
# Dockerfile — optimized layer order
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./ # deps first, cached
RUN npm ci --omit=dev
COPY . . # source last, changes often
CMD ["node", "server.js"]
# Distroless (smallest, most secure)
FROM node:20 AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM gcr.io/distroless/nodejs20
COPY --from=build /app/dist /dist
CMD ["/dist/server.js"]
# BuildKit with cache mount
# syntax=docker/dockerfile:1
FROM node:20-alpine
RUN --mount=type=cache,target=/root/.npm npm ci
Common Problems
- ⚠Large image — no .dockerignore, node_modules and .git included
- ⚠Slow builds — wrong layer order, source copy invalidates deps cache
- ⚠Configuration drift between environments — use infrastructure-as-code (Terraform/Pulumi) to keep dev/staging/prod identical
Best Practices
- ✓Copy package.json before source — deps layer cached unless deps change
- ✓Use distroless for production — no shell, no package manager, minimal attack surface
- ✓Set up health checks and graceful shutdown — handle SIGTERM to drain in-flight requests before exit