How to build and optimize Docker images for smaller, faster builds
Build lean Docker images by ordering layers for cache reuse, trimming the build context with .dockerignore, and choosing smaller base images. Measure results with docker history.
Build and optimize Docker images
A Docker image bundles your application and its runtime into a portable artifact. Large images are slow to build, push, and pull, and they expand the attack surface. This tutorial shows how to keep images small and builds fast through layer ordering, a focused build context, and lean base images.
Prerequisites
- Docker installed and running (
docker --version). - An application directory with source code and a dependency manifest.
Steps
1. Write a baseline Dockerfile
Start with a working image, then improve it:
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
Build it: docker build -t myapp:base .
2. Add a .dockerignore
The build context is sent to the daemon before the build starts. Exclude files you never need inside the image:
node_modules
.git
*.log
dist
.env
This speeds up COPY . . and avoids leaking secrets or local artifacts.
3. Order layers for cache reuse
Docker caches each instruction. Copy dependency manifests and install before copying the rest of the source, so code changes do not invalidate the dependency layer:
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]
Now editing server.js reuses the cached npm ci layer.
4. Choose a smaller base image
Variants like -slim or Alpine cut tens to hundreds of megabytes. Verify your app runs on the chosen libc (Alpine uses musl, which can break native modules). Pin a specific tag for reproducibility, for example node:20.11-slim.
5. Clean up in the same layer
Deleting files in a later RUN does not shrink earlier layers. Combine install and cleanup:
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
6. Measure image size
Compare before and after:
docker images myapp
docker history myapp:latest
docker history shows each layer's size so you can target the biggest contributors.
Verification
Run docker build -t myapp:opt . twice. The second build should reuse cached layers and finish quickly. Confirm the optimized image is smaller with docker images. Start a container with docker run --rm -p 3000:3000 myapp:opt and check the app responds.
Next Steps
Move to multi-stage builds to strip build tools from the final image, then explore distroless base images for the smallest, most secure runtime. Add image scanning to your pipeline to catch vulnerabilities in base layers.
Prerequisites
- Docker installed
- A simple application to containerize
Steps
- 1Write a baseline Dockerfile
- 2Add a .dockerignore
- 3Order layers for cache reuse
- 4Choose a smaller base image
- 5Clean up in the same layer
- 6Measure image size