2HAASStart a project
All posts

Guides

Reduce Docker image size with multi-stage builds

Large Docker images slow down every build, push and deploy. Multi-stage builds, slim base images and a .dockerignore file can cut an image to a fraction of its size.

The 2HAAS team2 min read

A Docker image holds everything a container needs: code, runtime, libraries and system tools. It is easy to let that grow past a gigabyte without noticing. The cost shows up everywhere: slower CI runs, slower deploys, higher registry bills and a larger surface for security issues.

Four changes remove most of the weight.

1. Use a multi-stage build

Compilers, dev dependencies and build caches are needed to build the app, not to run it. A multi-stage build keeps them in a first stage and copies only the result into a clean final image.

# Stage 1: build
FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: run
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]

The final image contains the compiled output and production dependencies. The TypeScript compiler, test tools and source files stay behind in the build stage.

2. Pick a smaller base image

The base image is often the largest layer.

Base image Rough size Good for
node:22 About 1 GB The build stage
node:22-slim About 200 MB Apps that need glibc
node:22-alpine About 150 MB Most Node.js services
Distroless Smaller still Locked down production images

Alpine uses musl instead of glibc. Native modules sometimes need extra packages there, so test before you switch.

3. Add a .dockerignore file

Without it, COPY . . sends everything in the folder to the build, including node_modules, .git and local environment files.

node_modules
.git
dist
.env*
*.log

This makes builds faster and keeps secrets out of the image.

4. Clean up in the same layer

Each RUN instruction creates a layer. Files deleted in a later layer still take up space in the earlier one. Install and clean up in a single instruction.

RUN apt-get update \
  && apt-get install -y --no-install-recommends curl \
  && rm -rf /var/lib/apt/lists/*

Check the result

Run docker images to compare sizes before and after, and docker history <image> to see which layer is the heavy one. A Node.js service that started above 1 GB commonly lands under 200 MB after these steps.

Smaller images are half of the story. For the other half, see how to speed up Docker builds with layer caching.

Keep reading

  1. Tech Tips2 min read

    Speed up Docker builds with layer caching

    Slow Docker builds delay every pull request. Order your Dockerfile for the layer cache, shrink the build context and use BuildKit cache mounts to cut build time.

  2. Guides2 min read

    Microservice observability: logs, metrics and traces

    When a request crosses ten services, one log file cannot explain a slow checkout. Logs, metrics and traces each answer a different question. Here is how they fit.

  3. Guides5 min read

    Enhance Web Security with Content Security Policy

    Learn how to implement a Content Security Policy (CSP) to protect your web applications from common attacks like XSS and data injection.

Tell us what’s in the way.

A few lines is enough. You get a candid yes, a candid no, or a sharper question.

  • Working software in week four
  • A reply in one business day
  • Handover is the deliverable

Prefer email? [email protected]

The problem, who it is for, and what is in the way right now.

One business day to a real reply. No sales sequence, no call required.