Skip to content

Offline builds in CI

Prefetch once, then build with no network — and the cache mistake that quietly undoes it.

Reed’s build is offline by design, with exactly one exception. Getting that guarantee in CI takes one setup step and one correctly configured cache.

Command Network
reed check Never. No prerequisites at all.
reed build None, given a prior successful reed prefetch on the same machine.
reed dev None, same condition.
reed prefetch The only command that touches the network.

reed check is unconditionally offline: it is a read-only validation pass over docs.json and your content, and it never invokes the dependency-install step at all. It is safe in any sandbox, with no prior setup, which makes it the right check to run on every pull request.

reed prefetch populates two separate caches, and CI has to restore both:

Directory Holds
REED_STORE_DIR (default ~/.reed/pnpm-store) the pinned dependency store
COREPACK_HOME / REED_COREPACK_HOME Corepack’s cache of the pinned package-manager binary

Restoring only one leaves a hidden network call on a cold runner. Restore only the store and the package-manager binary is fetched; restore only the Corepack cache and the dependency store is cold. Either way the build still succeeds — on a networked runner — so the leak goes unnoticed until it runs somewhere without egress.

yaml
env:
REED_STORE_DIR: ${{ github.workspace }}/.ci-cache/reed-pnpm-store
COREPACK_HOME: ${{ github.workspace }}/.ci-cache/corepack
REED_COREPACK_HOME: ${{ github.workspace }}/.ci-cache/corepack
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: Restore both reed caches
uses: actions/cache@v4
with:
path: |
${{ env.REED_STORE_DIR }}
${{ env.COREPACK_HOME }}
key: reed-${{ runner.os }}-${{ hashFiles('docs.json') }}
restore-keys: reed-${{ runner.os }}-
- run: reed check
- run: reed prefetch
- run: reed build --out dist

Node 22 rather than latest is deliberate: prefetch and build shell out to Corepack, and Node 25 and newer no longer bundle it. On a runner without Corepack, reed build fails fast with a Reed diagnostic saying so, rather than silently reaching the network.

sh
reed build --out dist | tee build.log # wrong

The exit status of a pipeline is the status of its last command, so a failing build piped into tee reports success. Redirect instead, or set set -o pipefail.

The offline guarantee covers exactly the platform, architecture and libc that prefetch ran on — and Linux distinguishes glibc from musl, so a standard CI image and an Alpine runner are two different targets. Run prefetch on each combination your pipeline actually uses.

An offline build is a claim until you measure it. Running the build inside a network namespace with no outbound access turns the claim into a test:

sh
unshare --user --map-root-user --net -- \
bash -c 'ip link set lo up 2>/dev/null || true; exec reed build --out dist'

If that passes, the build genuinely does not need the network. If it fails, something reached out — better to learn it here than in an air-gapped environment.