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.
What is offline, and when
Section titled “What is offline, and when”| 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.
The two caches
Section titled “The two caches”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.
A GitHub Actions setup
Section titled “A GitHub Actions setup”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 distNode 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.
Do not pipe a Reed command
Section titled “Do not pipe a Reed command”reed build --out dist | tee build.log # wrongThe 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.
Prefetch is per platform
Section titled “Prefetch is per platform”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.
Prove it, don’t assume it
Section titled “Prove it, don’t assume it”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:
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.