Safeguard
Supply Chain•

What should you pin to make a data pipeline environment reproducible?

Pin the interpreter, every package with hashes, JVM coordinates, OS packages, the base image digest and tool versions. A table of layers and what drifts.

Safeguard Security Team
4 min read

To rebuild a data pipeline environment as it was, pin every layer: the Python or JVM runtime, each package including transitive ones, OS packages, the base image by digest, and the orchestrator and its providers. A pin at one layer does nothing if another layer is free to move.

Definition

A reproducible environment is one you can rebuild later and get the same software. That is related to, but not the same as, a reproducible build, where the output is bit for bit identical; see hermetic vs reproducible build. For pipelines the practical target is smaller: the same package versions, so a rerun means the same code touched the data.

The layers

LayerWhat driftsHow to pin
Interpreter or JVMMinor upgrades in a base imageFixed version in the image or environment file
Python packagesNewer releases, new transitive pullsLockfile with exact versions and hashes
JVM packagesMaven resolution at submit timeExact coordinates, or a built jar
OS packagesPackage manager updatesVersion in the install command
Base imageA tag that movesDigest
Orchestrator and providersSeparate release trainsConstraints file, pinned image
Pipeline codeA branch that movesTag or commit

Several rows have a documented example. Spark's submitting applications guide says transitive dependencies are resolved from the coordinates you give. AWS Glue's docs say that pip resolves from PyPI on every run when you use only a module list. Databricks advises pinning OS package versions in init scripts for reproducible clusters. Beam suggests a curated or compiled requirements file, or a custom container.

A build order that works

  1. Choose the interpreter version and write it down.
  2. Write a short file of direct dependencies.
  3. Compile it to a fully pinned lockfile in CI.
  4. Install with hash checking, so a changed file fails the build.
  5. Build the image from a base referenced by digest.
  6. Install OS packages with explicit versions.
  7. Tag the resulting image and keep the lockfile with it.
  8. Run the same scan on the lockfile and the image.

What pinning does not do

Pinning stops surprise changes. It also stops surprise fixes. A pinned environment that nobody rebuilds drifts into vulnerability. So pair every pin with a schedule: rebuild, test, and review the diff, on a cadence you can defend. A pin is a decision, not a freeze.

Also record what actually ran. Keep the resolved package list per job run, so that when a new advisory lands you can search runs, not just repositories.

Try it yourself

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

Click through the inventory and count how many packages sit below the ones you declared. Those are the pins that are easy to miss.

Safeguard's SBOM and dependency scanning builds this inventory; try the free scan at gold.safeguard.sh/scan.

Quick check

Is a lockfile enough?

It covers Python or JVM packages only. The interpreter, OS packages and base image can still change, so pin those too.

How often should I rebuild a pinned environment?

On a schedule you choose and can defend, plus when a relevant advisory lands. Pinning without rebuilding preserves known vulnerabilities.

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.