Safeguard
Developer Tools•

Dev tools in the production image: drift you can remove today

Dev, staging, and prod often install different dependency sets. Omit dev packages in production builds and compare the result to cut attack surface.

Safeguard Security Team
3 min read

Production should install only what it needs to run. When dev and CI install test frameworks, linters, and build tools, and production images accidentally include them too, you carry extra packages and extra advisories. Install runtime dependencies only in production builds, and compare the lists to confirm.

Where this kind of drift comes from

Environments legitimately differ in dependency sets: developers need test runners, production does not. The drift is the unintended part. A Dockerfile that runs a full install in the final stage, or a deploy script that copies a whole working directory, brings dev packages along.

The npm documentation shows how the tool treats this. For npm ci, the omit setting defaults to dev when the NODE_ENV environment variable is set to production, otherwise it is empty. It also notes that omitted types are still resolved and recorded in the lockfile, they are just not physically installed. So the lockfile alone cannot tell you what is on disk, and an environment variable can change what gets installed.

Why it matters for security

Dev packages are still third party code. A malicious or vulnerable build tool inside a production image is a risk with no benefit. Scanners and customers asking for your SBOM will also see it, leading to findings you then have to explain.

ApproachResult
One image for all environments, dev tools includedLargest surface, simplest build
Multi-stage build, runtime only in the final stageSmaller surface, slightly more setup
Separate dev and prod dependency groupsClear intent, needs discipline

Steps to fix it

  1. List what is installed in the production image today, and mark the packages that exist only for tests or builds.
  2. Move build steps into an earlier stage of a multi-stage build. Copy only the output into the final stage.
  3. Make the final stage install runtime dependencies only.
  4. Do not rely on an environment variable alone. Make the production install command explicit, and check the result.
  5. Add a check that fails if known dev packages appear in the production SBOM.
  6. Compare dev, CI, staging, and production package lists after the change. Differences should now be explainable.

Do not hide the dev side

Dev dependencies still deserve scanning. A compromised test tool can run on developer laptops and CI runners, where secrets live. Scan them, but triage them separately from what ships. For SBOMs, mark component scope so readers can tell the groups apart; our post on CycloneDX component scope covers that.

Try it yourself

Click through it at your own pace, or open it full screen.

Look at the full inventory and imagine splitting it into runtime and dev groups. That split is the target.

What Safeguard does here

Safeguard scans dependency manifests and images, produces SBOMs, and monitors them continuously, so unexpected packages in a production artifact stand out. Try the free scan at gold.safeguard.sh/scan.

Quick check

Does omitting dev packages remove them from the lockfile?

No. The npm docs say omitted packages are still resolved and recorded. They are only not installed.

Is a smaller image always safer?

Usually it has fewer things to go wrong, but verify by scanning. Size is a proxy, not a guarantee.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.