Search Composal documentation

Search public documentation, commands, and release notes.

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:

YAML
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:

JSON
{
  "$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:

Bash
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:

YAML
- 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:

YAML
- 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:

JSON
{
  "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:

YAML
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

SymptomCheck
The action requires a Composal managed Linux runnerUse a configured Composal runner label. GitHub-hosted runners and job containers without the mounted disk are unsupported.
Turbo reports remote caching disabledRun Turbo after the action, remove step-level overrides of its exported variables, and check that your command has not disabled remote caching.
Every run missesKeep 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 missingAdd the generated paths to that task's outputs. Cached logs alone do not restore undeclared files.
A new PR starts coldEach PR merge ref has a separate store. The default branch's artifacts are not a fallback.
A concurrent job cannot see a new artifactWait for the producer job to finish and use a fresh consumer runner. Existing mounted views do not automatically reload.
Signature verification failsCheck 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 failsCheck 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

SettingMeaning
key inputOptional stable cache name. Defaults to turborepo-v0; at most 512 characters, without newlines.
api outputThis job's loopback server URL.
path outputPersistent artifact directory on the mounted filesystem.
AuthenticationRandom token for this job, automatically exported to subsequent steps.
Artifact limit1 GiB per upload.
RetentionPersistent 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.