---
title: "Keep build tools out of the runtime image · Agent Wranglers"
url: https://agentwranglers.se/playbook/operations/containers/multi-stage-builds
updated: 2026-10-20
licence: "All rights reserved"
---

# Keep build tools out of the runtime image

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Containers](https://agentwranglers.se/playbook/operations/containers)
- Rule: Build in one stage and copy only the finished app into a clean final stage. Compilers, test tools and development packages never reach the image you ship.
- How: Install all packages and run the tests in a build stage. Start the final stage from the base image again. Install production packages only, and copy the built files with `COPY --from=build`. Leave the package manager, such as npm or pip, out of the final stage, or start it from a distroless image. Scanners often find holes in the package manager itself, and the running app never needs it.
- Check: `docker run --rm --entrypoint sh shop -c 'command -v npm pip apk apt-get gcc'` prints nothing, or the image has no shell at all. A scan of the final image lists no test or build packages.

Example:

```dockerfile
# Node: build and test, install production packages, then ship Node without npm
FROM ${NODE_IMAGE} AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm test && npm run build

FROM ${NODE_IMAGE} AS packages
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

FROM ${NODE_IMAGE}
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /opt/yarn-* \
  /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack /usr/local/bin/yarn /usr/local/bin/yarnpkg
WORKDIR /app
COPY --from=packages /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
```

```dockerfile
# Go: one static binary on a distroless base with no shell and no package manager
FROM golang:1.26-alpine@sha256:<digest> AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go test ./... && CGO_ENABLED=0 go build -trimpath -o /shop ./cmd/shop

FROM gcr.io/distroless/static-debian12:nonroot@sha256:<digest>
COPY --from=build /shop /shop
ENTRYPOINT ["/shop"]
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers operations playbook: https://agentwranglers.se/playbook/operations/containers/multi-stage-builds`.

- Previous: [Pin each base image by digest](https://agentwranglers.se/playbook/operations/containers/pinned-base-images)
- Next: [Run containers as a user without root](https://agentwranglers.se/playbook/operations/containers/non-root-containers)
- [Containers](https://agentwranglers.se/playbook/operations/containers)
