█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/DevOps and Infrastructure/Docker and Containerization/Optimizing Docker Images
40 minIntermediate

Optimizing Docker Images

After this lesson, you will be able to: Apply multi-stage builds, optimize layer caching, minimize image size, and integrate security scanning into the build.

The difference between a 1.5GB Docker image and a 50MB one is one or two file edits. Production images should be small, fast, and signed.

Prerequisites:Docker Hub and Registries

Multi-stage build (the biggest single win)

Build in one stage with full tooling; copy only the artifact into a slim final stage.

tsx
# syntax=docker/dockerfile:1
# Stage 1: build (has compilers, dev deps, source)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: runtime (only what you need to RUN)
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
# Final image contains: alpine OS + node runtime + production deps + your dist/.
# No source code, no devDependencies, no build tooling. Often 5-10x smaller.

Base image choice matters

node:20 → about 1GB (Debian-based, lots of tools). node:20-slim → about 200MB (smaller Debian). node:20-alpine → about 150MB (Alpine Linux, musl libc). node:20-alpine + multi-stage → about 100MB total. distroless (gcr.io/distroless/nodejs20-debian12) → no shell, minimal attack surface, often <100MB. Used by Google in production. Alpine gotcha: musl libc differs from glibc; some native modules (sharp, bcrypt) need extra setup. If you hit weird errors, try slim before chasing the alpine error.

Layer caching tricks

Same rule as the Dockerfile lesson: least-changing first, most-changing last. Combine related RUN commands: `RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*` is one layer; three RUNs would be three. Use BuildKit cache mounts for package caches: `RUN --mount=type=cache,target=/root/.npm npm ci`. Massively faster local builds. Avoid `COPY . .` early. Use a `.dockerignore` that excludes `node_modules`, `.git`, `tests/`, `*.md`.

Security: drop root, scan, sign

Three habits that turn casual images into production-grade.

tsx
# In Dockerfile: never run as root
RUN addgroup -S app && adduser -S app -G app
USER app
# Scan the built image
trivy image myapp:1.0
docker scout quickview myapp:1.0 # built into Docker Desktop
# Sign the image (Cosign, supply-chain security)
cosign sign --key cosign.key myapp:1.0
# Receiver can verify: cosign verify --key cosign.pub myapp:1.0
# In CI:
# - run Trivy on every PR
# - fail the build on Critical CVEs
# - sign on every release tag

Common mistakes only experienced engineers avoid

Single-stage builds with full toolchain in production. Bloated + insecure. `COPY . .` then `RUN rm -rf node_modules`. Doesn't shrink the layer; the deleted file is still in the previous layer. Use multi-stage or .dockerignore. Running as root. Most CVEs assume non-root containers; running as root multiplies the blast radius. Skipping security scans. Outdated base images are the #1 source of CVEs in container deploys. Trusting `:latest` of any base image. Pin to a specific version + digest for reproducibility.

Quick Check

How does a multi-stage build reduce final image size?

Pick the cleanest answer.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Docker Hub and Image Registries
Back to Docker and Containerization
Running Real Applications in Docker→