Turborepo cache
Cache Turborepo build outputs on Composal managed runners. Set up composalai/turborepo, configure task outputs, verify cache hits, and troubleshoot misses.
Contents
Reuse Turborepo task outputs between CI jobs with composalai/turborepo@v0.
The action starts a remote cache server on localhost and stores artifacts directly
on the persistent Modal Volume v2 filesystem mounted on Composal managed runners.
Later jobs can restore matching outputs instead of running the same task again.
Add the action to your workflow
First configure a Composal runner pool for your repository. Your project must already have Turborepo installed and a working build task. Add the action before the first Turbo command:
name: Build
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: [composal-x64-4x]
steps:
- uses: actions/checkout@v7
- uses: composalai/turborepo@v0
- run: npm ci
- run: npx turbo run build
Keep your existing package manager and install step. For a pnpm project, use
your usual pnpm setup, pnpm install --frozen-lockfile, and
pnpm exec turbo run build instead of the npm commands above.
The action exports TURBO_API, TURBO_TEAM, and a job-local TURBO_TOKEN for
subsequent steps. You do not need turbo login, turbo link, a Vercel account,
or a remote-cache token secret. Remove explicit overrides of these three variables
from your build step so it uses the local endpoint. The token and URL belong to
this job; do not save them for another job or a developer machine.
The server starts from a bundled distribution, checks readiness, and listens only on loopback at an available port. It runs on both Composal VM and sandbox Linux runners without Docker. Its post step stops the server and flushes writes before the job finishes.
Tell Turbo which files to restore
Declare the files produced by each cacheable task in turbo.json. For a build
that writes dist/ within each package:
{
"$schema": "https://turborepo.dev/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
Replace dist/** with your actual output paths. For a Next.js build, a common
choice is [".next/**", "!.next/cache/**"]. Keep the checkout, restored files,
and local .turbo directory on the runner's local disk. The action persists
the remote artifact store; you do not need to mount these directories yourself.
Cache tasks whose outputs are determined by their inputs. Declare environment
variables that affect build output in the task's env or in globalEnv, and
include other relevant input files in Turbo's configuration. Tasks that deploy,
publish, or require fresh external state should not reuse a cached result.
See Turbo's caching guide
and environment variable guide
for task configuration.
This cache reuses completed tasks. It does not add source watching, hot reload, or incremental compilation to a task that does not already support them.
Verify a remote cache hit
Run a build once to populate the cache. Then rerun the workflow on the same Git ref with unchanged task inputs and the same action key. A fresh runner has no local build cache, so restored task outputs demonstrate reuse between jobs. Check Turbo's summary for cached tasks and confirm the expected output files exist.
To test only the remote cache, temporarily use:
npx turbo run build --cache=remote:rw --summarize
The first run should miss for tasks without existing artifacts. The next matching
run should hit and restore their declared outputs. --summarize writes run
details under .turbo/runs/; compare these when a task unexpectedly rebuilds.
Keep the default local and remote cache behavior for ordinary builds unless you
specifically want the remote-only check.
Choose a stable cache key
The default key is turborepo-v0. Use a stable project name when a repository
contains multiple independent Turbo projects, or change the key to start fresh:
- uses: composalai/turborepo@v0
with:
key: storefront-v1
Do not include a commit SHA, run ID, or timestamp in a normal cache key: changing the key starts a separate store. Turbo already hashes task inputs to decide which results it can reuse.
Stores are scoped by GitHub installation, repository, key, and GITHUB_REF.
Jobs on the same ref reuse artifacts across workflow runs and runner sizes.
Different branches, tags, and pull request merge refs use separate directories;
a pull request does not inherit the default branch's cache. Reruns on that pull
request can reuse artifacts whose task hashes still match.
Concurrent jobs with the same scope share writable storage. Completed artifacts from failed jobs can remain available. New runners see committed writes, but an already-running runner does not automatically refresh its view of another runner's writes. Let a producer job finish, including its post step, before starting a consumer job that must read its new artifacts.
Changing a key leaves the old store in place. V0 has no automatic eviction or user-facing cache deletion command.
Cache dependency downloads too
The Turbo action caches task outputs. Use the disk-volume action separately for package downloads if you want to speed up installation too:
- uses: actions/checkout@v7
- uses: composalai/disk-volume@v1.2.0
with:
key: npm-store-v1
path: ~/.npm
- uses: composalai/turborepo@v0
- run: npm ci
- run: npx turbo run build
Always keep the install step. The npm store supplies downloads; Turbo supplies
matching task results. Avoid separately archiving the same remote artifact store
with actions/cache or putting node_modules, .git, or the workspace root
on a dependency disk. See dependency caches
for the disk action's sharing rules and limits.
Sign artifacts used by trusted builds
Ref directories separate normal cache reuse, but the mounted volume is writable by workflow code. They are not an access boundary against untrusted jobs. Never cache credentials or secret-bearing output files or logs.
To verify restored artifacts, add this setting alongside your existing tasks:
{
"remoteCache": {
"signature": true
}
}
Set TURBO_REMOTE_CACHE_SIGNATURE_KEY to the same secret for the action and
your Turbo command. Use a randomly generated secret of at least 32 bytes, and
make it available only to trusted jobs. For example, in a workflow triggered
only by trusted pushes:
jobs:
build:
runs-on: [composal-x64-4x]
env:
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
steps:
- uses: actions/checkout@v7
- uses: composalai/turborepo@v0
- run: npm ci
- run: npx turbo run build
The action enables signature-tag storage without giving the signing secret to the cache server. Turbo signs and verifies on the client. Fork pull request jobs generally do not receive repository secrets; do not treat an absent secret as a signed-build configuration. See Turbo's artifact authenticity guidance.
Troubleshoot cache misses and failures
| Symptom | Check |
|---|---|
| The action requires a Composal managed Linux runner | Use a configured Composal runner label. GitHub-hosted runners and job containers without the mounted disk are unsupported. |
| Turbo reports remote caching disabled | Run Turbo after the action, remove step-level overrides of its exported variables, and check that your command has not disabled remote caching. |
| Every run misses | Keep the key and ref stable. Compare run summaries for changed source, lockfiles, task configuration, build environment, or command arguments. Do not use --force when testing reuse. |
| Turbo reports a hit, but build files are missing | Add the generated paths to that task's outputs. Cached logs alone do not restore undeclared files. |
| A new PR starts cold | Each PR merge ref has a separate store. The default branch's artifacts are not a fallback. |
| A concurrent job cannot see a new artifact | Wait for the producer job to finish and use a fresh consumer runner. Existing mounted views do not automatically reload. |
| Signature verification fails | Check the trusted job's signing key and remoteCache.signature setting. Use the same signing key for matching producers and consumers; never bypass verification to accept an artifact. |
| An artifact upload fails | Check artifact size: v0 accepts at most 1 GiB per artifact. Review declared outputs for files that should not be cached. |
The action must report readiness before the build begins. Its logs identify
startup and post-step flush failures. Do not print TURBO_TOKEN or the signing
key when collecting diagnostics.
Inputs, outputs, and limits
| Setting | Meaning |
|---|---|
key input | Optional stable cache name. Defaults to turborepo-v0; at most 512 characters, without newlines. |
api output | This job's loopback server URL. |
path output | Persistent artifact directory on the mounted filesystem. |
| Authentication | Random token for this job, automatically exported to subsequent steps. |
| Artifact limit | 1 GiB per upload. |
| Retention | Persistent storage with no automatic eviction in v0. |
There is no action-level cache-hit output: Turbo determines hits separately
for each task. An existing directory does not establish that a task can be
restored. The server uses
ducktors/turborepo-remote-cache
2.14.3 with local filesystem storage. The
published action includes its bundled
runtime and third-party license notices.