Build verifiable artifacts

Build from clean inputs

Create releases in an isolated environment from reviewed source, locked dependencies, declared tools, and no hidden workstation state.

A release should come from source and declared inputs, not from whatever happened to exist on one laptop.

A release built on a laptop quietly includes an untracked generated file and a globally installed compiler. Nobody reviewing the repository can reproduce those inputs, and nobody can say whether the laptop was clean. This is why SLSA and similar frameworks push builds onto ephemeral builders: fresh environments that exist for one build and are destroyed after.

A minimal clean release job on GitHub Actions declares every input:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
      - run: npm ci
      - run: npm run build

The runner starts empty. The source is one exact revision. The Node version comes from a committed file, not from whatever is on someone’s machine. npm ci installs the locked graph deterministically. Nothing undeclared can leak in, because nothing undeclared exists on the runner.

Keep release credentials out of normal test steps: the build job here holds no publishing token at all. Record the source revision and build identity with the artifact — in Actions, github.sha and the workflow run URL are the minimum worth saving.

Verify with a double build

Build the same revision twice in separate clean environments and compare digests:

shasum -a 256 dist/app.tar.gz
# 9f2c1e40b3a8...  dist/app.tar.gz

If the two digests match, your declared inputs fully determine the output. If they differ, compare the artifacts and explain the first meaningful difference.

Two clean builds can still differ because of timestamps, file order, or tool behavior. Reproducibility is evidence to investigate, not a promise produced by an ephemeral runner. Archive timestamps embedded by tar or zip are the most common culprit in JavaScript projects.

Build the same revision twice in separate clean environments and save both artifact digests plus the declared tool versions. If the digests differ, compare the artifacts and explain the first meaningful difference. Add an undeclared local file and prove the clean build ignores it or fails explicitly.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →