Dev iconDevSep 10, 2026 ~5 min source read

Multi-stage Docker builds: ship the artifact, not the build shop

Use multi-stage Dockerfiles to separate building from the runtime image so you ship only the compiled artifact and a minimal runtime. Smaller images mean faster pulls, smaller attack surface, and fewer vulnerable layers.

Multi-stage Docker builds: ship the artifact, not the build shop

Share this story

Send the public story page.

Useful takeaways from this story.

Do the compile and install work in an early named stage, then COPY only the artifact into a smaller final stage.

Keep dependency install before copying full source so Docker cache survives small code changes.

Pick the smallest suitable base (slim or distroless) and run as a non-root user to reduce size and attack surface.

# Why change your Dockerfile Most single-stage Node Dockerfiles keep the entire build shop in the final image: the Debian base, full Node toolchain, compilers, dev dependencies, npm cache, and your full source tree. That often turns a tiny app into a 1+ GB image. The consequences: slower pulls during deploys and autoscaling, more layers for scanners, larger attack surface, and longer time between push and running.

# What multi-stage builds do A multi-stage Dockerfile uses multiple FROM instructions. Do the heavy work in an early stage (install, compile), then start a fresh final stage and copy only what you actually need to run. The build-stage layers never make it into the final image.

  • Stage 1 (named build): FROM node:20 AS build — install dependencies including dev, run build steps.

This pattern keeps compilers, dev packages, npm cache, and full source out of the runtime image.

# Four implementation details that matter

2) Don't copy node_modules between stages

3) Order instructions to maximize cache reuse Docker caches layers top to bottom. Copy lockfiles and install dependencies before copying the rest of your source. That way a code change invalidates only the later layers and doesn't force a full reinstall.

If you reverse that, every small change triggers a full dependency reinstall.

4) Choose a smaller final base and drop root

# Other useful patterns

  • Multiple build targets: add a test stage (FROM build AS test) that runs linters and unit tests. CI can build only that --target test.

# What you get in practice Measured on an Express + TypeScript example in the article:

  • Single-stage node:20: ~1.1 GB
  • Multi-stage with node:20-slim and --omit=dev: ~180 MB
  • Multi-stage with distroless final: ~130 MB

# Practical checklist before you change a Dockerfile

  • Add a build stage: FROM node: AS build.
  • Install and build in build stage.
  • Keep install layer above COPY.. to preserve cache.
  • Use a non-root USER in the final stage.

A small, ten-line change yields smaller images, faster deploys, and fewer runtime liabilities.

More context around this story.

The Startup Time Trick Hiding Inside Your Docker Build
Dzone iconDzoneSep 3, 2026

The Startup Time Trick Hiding Inside Your Docker Build

Every Java developer who runs services on Kubernetes has watched this scene play out. Traffic spikes, the autoscaler adds a pod, and then everyone waits. The container is running in two seconds. The application is not ready for another twelve seconds. During those ten seconds, your existing pods absorb the extra load,

Containerizing Spark and Lakehouse Development with Docker
Dzone iconDzoneAug 25, 2026

Containerizing Spark and Lakehouse Development with Docker

Most Docker content targets web developers shipping stateless services. However, data engineers, who represent a huge and growing population of Dockers users, are mostly left to figure things out alone, and it shows. The get pipelines that pass locally, but explode on clusters. They pit notebook-only development agains

Deploying code and releasing a feature are two different days
Dev iconDevSep 1, 2026

Deploying code and releasing a feature are two different days

Everything we run ships continuously. The engine behind the apps, the back office our customers build in, the web products around them — code moves to production at the rhythm of engineering: when it's ready, it goes. Features don't work that way. A feature has a launch: a name, documentation, support people who know i

I Used Docker Before I Understood It
Dev iconDevSep 7, 2026

I Used Docker Before I Understood It

I've used Docker several times. The funny part? I didn't really understand Docker. My first encounter with it was back in March 2026, when I was exploring different automations and customizations. One of the things I wanted to try was running Ollama with WebUI locally. I wanted a local AI model with a nice interface, a

Loading more related stories...

Keep reading in the app

Open the app view to save this story, compare related coverage, and continue from the same source.

Open in app