hammerkit
Guides

Caching strategy in CI

Reuse build results across CI runs — backends vs. store/restore.

Locally, hammerkit caches automatically: a task whose src is unchanged is skipped. The challenge in CI is that each run usually starts from a fresh checkout on a fresh machine, so there is no local cache to hit. This guide covers the two ways to carry results across CI runs and which to choose.

Keep the cache method at the default checksum in CI. modify-date compares file timestamps, which git clone resets on every run — so a modify-date task never hits the cache on a fresh runner. See caching.

Point the built-in default cache at an S3-compatible bucket (or a container registry — see caches). Hammerkit then pulls a task's result before running it and pushes the result after — no cache scripting in your pipeline at all.

.hammerkit.yaml
caches:
  default:
    method: checksum
    backend:
      type: s3
      bucket: my-build-cache
      region: eu-central-1

tasks:
  build:
    image: node:24-alpine
    src:
      - src
    generates:
      - dist
    cmds:
      - npm run build

Credentials come from the standard AWS SDK chain (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY), so set those as CI secrets. Backend errors never fail the build — an unreachable bucket just falls back to running the task. The same bucket works for developers locally, so a result built on a laptop is reused in CI and vice versa. See caches for the full backend reference (MinIO, R2, GCS).

Sharing across checkouts, runners and agent sandboxes

Cache ids are computed from paths relative to the project root (the git root), so the same commit produces the same ids wherever it is checked out — a CI runner at /home/runner/work/app/app, an agent sandbox at /workspace/app and a laptop all hit the same entries. Use container tasks for anything you want to share between macOS and Linux machines: a local task includes the host OS in its identity.

Read-only runners

Only trusted runners should write to the shared cache. Give untrusted runners — coding-agent sandboxes, pull requests from forks — read-only credentials and run them in read-only mode, so they restore results but never push:

export HAMMERKIT_CACHE_READ_ONLY=1   # or pass --cache-read-only to run / up
hammerkit run test

Read-only mode skips every backend push, including the built-in local default cache. Results a read-only run builds are still reused by later runs in the same checkout.

Splitting network from compute

Instead of letting every task talk to the bucket during the build, keep tasks on the machine-local default cache and move entries explicitly with cache pull / cache push. The build itself then does no network I/O, and a workspace can be warmed before anyone runs anything:

hammerkit cache pull --remote shared     # network only
hammerkit run                            # compute only
hammerkit cache push --remote shared     # trusted runners only

Option B — store / restore with the CI's own cache

If you'd rather use your CI provider's built-in caching, wrap the run with store / restore: restore pulls the previous results out of a directory before the build, store writes them back after, and the CI caches that directory.

hammerkit restore cache   # before the build
hammerkit build
hammerkit store cache      # after the build

A GitHub Actions job wiring that to actions/cache:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '24'
      - run: npm i -g hammerkit
      - uses: actions/cache@v4
        with:
          path: cache
          key: hammerkit-${{ hashFiles('**/package-lock.json') }}
          restore-keys: hammerkit-
      - run: hammerkit restore cache
      - run: hammerkit build
      - run: hammerkit store cache

Which one?

Remote backendstore / restore
Pipeline wiringNone — automatic pull/pushYou add restore/store steps + a cache step
GranularityPer taskOne directory for the whole run
Shared with local devYes (same bucket)No (CI cache only)
Needs a bucket or registryYesNo

A remote backend is the lower-maintenance choice and is shared with local development. If you already rely on your CI's cache and don't want a bucket, store/restore is fine. You rarely need both — if a remote backend is configured, store/restore is redundant.

On this page