Safeguard
Developer Tools•

Why does my build differ on my laptop and in CI?

Builds differ between laptop and CI when inputs are not pinned: tool versions, dependencies, system packages, environment. Here is how to find the cause.

Safeguard Security Team
4 min read

A build differs between your laptop and CI because something it reads is not the same in both places. Usual suspects are the toolchain version, dependency resolution, system packages, environment variables and the clock. Find the differing input first, then pin it so both machines read the same thing.

Define the problem before fixing it

"Different" covers two cases. In the first, the build passes on one machine and fails on the other. In the second, both succeed but the output files differ. The first is annoying. The second matters if you ever want to prove what you shipped.

The reproducible-builds project defines a reproducible build as one where, given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of the specified artifacts. Read that as a checklist of three things to hold constant: source, environment, instructions. When two machines disagree, one of those three is not actually constant.

Where the drift usually hides

InputHow it driftsWhat to do
Language runtime and build toolLaptop has whatever was installed last year, CI uses the runner imageDeclare the version in the repo and make both read it
Dependency versionsInstall resolves new releases when no lockfile is honouredCommit the lockfile and use the frozen install command in CI
System packagesPackage index moves on each image buildPin the base image and the package set
EnvironmentLocale, timezone, umask, username, pathSet them explicitly in the build script
Time and randomnessBuild date, UUIDs, random seeds written into outputRemove them or fix them from the source revision

Step 1: make CI and laptop run the same command

Many gaps are simply two different commands. A developer runs a local script with extra flags, CI runs a YAML list of steps that grew separately. Move the real build into one script in the repository, and have CI call exactly that script. Then a difference must come from the environment, not the instructions.

Step 2: freeze dependency resolution

For Node projects, the npm documentation says the clean install command requires an existing lockfile, exits with an error if the lockfile and package.json disagree instead of updating the lockfile, and never writes to package.json or the lockfiles. That is the behavior you want in CI: a mismatch becomes a loud failure rather than a quiet upgrade. Other ecosystems have their own frozen modes, so check what yours calls it.

Step 3: compare, do not guess

Run the build twice, once on each machine, then compare file hashes of the output. If you just care about pass or fail, compare tool versions and the dependency tree printed by each machine. A short log of versions at the start of every CI run costs nothing and turns a mystery into a diff.

# print the facts that most often differ
uname -a
env | sort | grep -E 'LANG|LC_|TZ'
your-build-tool --version
sha256sum dist/*

Step 4: remove the inputs you cannot pin

The reproducible-builds documentation says timestamps are the biggest source of reproducibility problems. If your build embeds the current date, either drop it or take it from the source revision. The same project suggests keeping timezones, locales and umasks clean or making sure they do no harm.

Try it yourself

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

Look at how a dependency inventory is built from the project's manifests and lockfiles. If two machines would produce different inventories, they are not building the same thing.

Where Safeguard fits

Safeguard reads manifests and lockfiles for SBOM and dependency scanning, so a drift in resolved versions shows up as a change in the inventory. You can try a free scan at gold.safeguard.sh/scan.

Quick check

Is a lockfile enough to make a build reproducible?

No. It pins dependency versions, but the compiler, system packages, environment and timestamps can still change the output.

Should I compare builds on every commit?

Not necessarily. Compare when you change the toolchain or base image, and before releases.

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.