Skip to content
Engineering · 5 min read

A dependency you cannot rebuild is a dependency you do not have

The claim The software your business runs is only as reliable as your ability to rebuild it from source, and most teams discover far too late that they cannot. The application depl...

A Written by Administrator
A dependency you cannot rebuild is a dependency you do not have

The claim

The software your business runs is only as reliable as your ability to rebuild it from source, and most teams discover far too late that they cannot. The application deploys fine as long as the existing servers keep running, but the exact versions that made it work were never recorded, the build depends on a developer's laptop configuration, or a critical package has vanished from the internet — and the day you need to rebuild from scratch, on a new machine, after a failure, you find that you cannot reproduce the thing you have been running for years. A build you cannot reproduce is a liability disguised as a working system.

Why "it runs" is not "you can rebuild it"

A running system and a reproducible one are different properties, and the gap between them is invisible until it is fatal. The running system works because a specific combination of package versions, system libraries, and configuration happened to come together once and has not been disturbed. Nobody wrote down that combination, because they did not have to — it was already running. Then the server fails, or you need to scale to a second machine, or a security patch forces a rebuild, and you are trying to recreate a state nobody recorded from memory and guesswork, under time pressure, while the system is down.

The failure modes are specific and common: the build pulls "the latest" version of a dependency, which is now a different, incompatible version than the one that works; a package the build needs has been removed from its registry by its author; the build only succeeds on one person's machine because of something installed there years ago that nobody documented. Each of these turns a routine rebuild into an archaeology project.

Pin every version, exactly

The foundation of reproducibility is that your build specifies the exact version of everything it depends on, and never the word "latest". A lockfile records the precise version of every direct and transitive dependency, so that a build today and a build in two years resolve to identical packages:

# commit the lockfile; it is the record of exactly what works
package-lock.json      # npm
poetry.lock            # python
Cargo.lock             # rust
# the rule: the lockfile is committed, and the build uses it verbatim
npm ci                 # installs exactly the lockfile, fails if it drifts

The distinction between npm install and npm ci captures the whole principle: the former may update versions, the latter installs exactly what the lockfile says and refuses if reality has drifted from the record. Your build should use the strict form, so that a build either reproduces the recorded state or fails loudly — never silently substitutes a different version.

Vendor what you cannot afford to lose

Pinning a version does not help if the package itself disappears, and packages do disappear — authors delete them, registries remove them, companies go under. For the dependencies your build genuinely cannot proceed without, keep your own copy rather than trusting that the public registry will always serve it. This can be a private registry mirror that caches everything your builds use, or vendoring the critical dependencies directly into your repository. The test is: if the public registry were unreachable for a week, could you still build and deploy? For anything where the answer must be yes, you need a copy you control.

Make the build environment itself reproducible

Pinned application dependencies are undermined if the environment around them — the operating system, the language runtime, the system libraries — is whatever happened to be on the build machine. The build should declare its environment as explicitly as it declares its dependencies, so that it produces the same result regardless of which machine runs it. A container image built from a pinned base is the common way to achieve this:

FROM node:22.9.0-bookworm-slim   # pinned, not node:latest
# the runtime, the OS, and the app deps are now all specified

With the environment pinned, the build stops depending on the accidental state of any particular machine, and "works on my laptop" stops being a category of problem, because there is no laptop-specific state left for it to depend on.

Store the artefact, not just the recipe

Even a perfectly reproducible build takes time to run, and during an incident time is the thing you do not have. Keep the built artefacts — the container images, the compiled binaries — in a registry you control, tagged by version, so that recovering a known-good release is pulling and running an existing artefact rather than rebuilding it from source under pressure. The reproducible build is your guarantee that you can recreate any version; the stored artefact is what lets you deploy it in seconds when the alternative is a ten-minute build you are running with shaking hands.

The test that proves it

The only way to know your build is reproducible is to reproduce it, deliberately, before you are forced to. Take a clean machine — one with none of your team's accumulated setup — and build and deploy your application from nothing but the source repository. If it works, you have a genuine ability to recover, and you have documented, by doing it, exactly what recovery requires. If it fails, you have found the missing pieces on a calm afternoon instead of during an outage, which is the entire point of the exercise. A build that has been reproduced from scratch on a clean machine is a build you actually have; one that has only ever run on the servers it was born on is a build you are borrowing from luck.

#build #dependencies #reproducibility #disaster recovery

Keep reading