---
title: Offline builds in CI
description: 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

| 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

`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

```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.

## Do not pipe a Reed command

```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`.

## 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

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.
