Docker Image Optimization: From 1GB to 100MB
Large Docker images slow down CI/CD pipelines, increase storage costs, and extend deployment times. Multi-stage builds, layer optimization, and base image selection can reduce image size by 80-90%.
Key Takeaways
- A 1 GB Docker image takes 30-60 seconds to pull on a fast network.
- Start with `-slim` variants for most production workloads.
- Multi-stage builds separate the build environment from the runtime environment.
- Each `RUN` instruction creates a new layer.
- A `.dockerignore` file prevents unnecessary files from entering the build context: `.git/` (often 100+ MB), `node_modules/`, `__pycache__/`, test fixtures, documentation, and IDE configuration.
Compress Image
Reduce image file size while keeping quality.
Why Image Size Matters
A 1 GB Docker image takes 30-60 seconds to pull on a fast network. In a CI/CD pipeline running 50 builds per day, that's 25-50 minutes of daily wait time just for image pulls. Smaller images also reduce attack surface (fewer installed packages = fewer vulnerabilities) and storage costs in container registries.
Base Image Selection
Size Comparison
| Base Image | Size | Use Case |
|---|---|---|
ubuntu:24.04 |
~78 MB | When you need apt-get and full OS |
python:3.13 |
~1 GB | Development, not production |
python:3.13-slim |
~150 MB | Production Python apps |
python:3.13-alpine |
~55 MB | Minimal Python (musl libc caveats) |
node:22 |
~1.1 GB | Development, not production |
node:22-slim |
~200 MB | Production Node.js apps |
gcr.io/distroless/python3 |
~50 MB | Maximum security (no shell) |
Start with -slim variants for most production workloads. Alpine images are smaller but use musl libc instead of glibc, which can cause subtle compatibility issues with some Python packages (numpy, pandas).
Multi-Stage Builds
Multi-stage builds separate the build environment from the runtime environment. Build dependencies (compilers, development headers) stay in the build stage and never appear in the final image:
# Build stage
FROM python:3.13 AS builder
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# Runtime stage
FROM python:3.13-slim
COPY --from=builder /root/.local /root/.local
COPY . /app
CMD ["python", "/app/main.py"]
The final image contains only the slim base + installed packages + your code. Build tools, source archives, and compiler output are discarded.
Layer Optimization
Combine RUN Commands
Each RUN instruction creates a new layer. Installing packages and cleaning up in separate RUN commands means the cleanup doesn't reduce image size — the deleted files still exist in a previous layer. Combine install and cleanup in a single RUN.
Order for Cache Efficiency
Copy dependency files (requirements.txt, package.json) before copying source code. Dependencies change less frequently than code, so Docker can cache the expensive installation layer and only rebuild when dependencies actually change.
.dockerignore
A .dockerignore file prevents unnecessary files from entering the build context: .git/ (often 100+ MB), node_modules/, __pycache__/, test fixtures, documentation, and IDE configuration. A bloated build context slows down every build even if the files aren't copied into the image.