🍋
Menu
How-To Beginner 2 min read 393 words

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.

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.