Turborepo Remote Cache — CI Speed Without Leaking Secrets
Turborepo’s remote cache can turn a 20-minute monorepo CI into 4 minutes by reusing build / test outputs across machines. It can also serve a poisoned artifact or exfiltrate env-bound output if you treat the cache as a dumb blob store. Speed is the point; input hashing and secret hygiene are the price of admission.
What gets cached
Turbo hashes task inputs (files, env keys you declare, relevant lockfile bits) and stores outputs you list in turbo.json. A cache hit restores those outputs and skips the command. A hit with the wrong env still “succeeds” if you forgot to declare the env var as an input — that is how “works in CI, wrong API URL baked into the bundle” happens.
{
"$schema": "https://turbo.build/schema.json",
"remoteCache": { "signature": true },
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"],
"env": ["NEXT_PUBLIC_API_URL"],
"passThroughEnv": ["CI"]
},
"test": {
"dependsOn": ["build"],
"env": ["DATABASE_URL"]
}
}
}
If NEXT_PUBLIC_* affects the build but is missing from env, two CI jobs with different values share a bad hit.
Wire CI without putting tokens in logs
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
# Vercel Remote Cache or self-hosted / Turborepo cache server
Rules:
- Store
TURBO_TOKENin the CI secret store, not in the repo. - Restrict token scope to the team/org; rotate on staff offboarding.
- Enable artifact signature when available so clients reject tampered payloads.
- Do not echo
turbodebug env in public forks PRs — fork PRs should get read-only cache or no remote cache.
Fork PR attack surface
Public repos: a malicious PR can try to poison the cache if write credentials are available to pull_request from forks. Prefer:
pull_requestfrom forks → local cache only / read-only tokenpushto main / trusted branches → read-write token
GitHub’s pull_request_target mistakes are especially dangerous here — do not hand write tokens to untrusted code.
What not to put in outputs
| Output | Risk |
|---|---|
.env files | Secret leak via cache download |
| Files containing absolute machine paths + tokens | Info leak |
| Test DBs / dumps | PII |
node_modules whole tree | Usually wrong; huge and platform-specific |
Keep outputs to build artifacts. Secrets belong in the environment at runtime, not in restored dist unless they are intentionally public (NEXT_PUBLIC_).
Debugging bad hits
turbo build --summarize
# or TURBO_LOG_ORDER=stream with dry run options per current CLI docs
Check the hash inputs: changed env? missing env key? unstable file in inputs (timestamps in generated files)? Pin generated code or exclude noise via inputs globs.
"build": {
"inputs": ["src/**", "package.json", "tsconfig.json"],
"outputs": ["dist/**"]
}
Self-hosted vs Vercel
Vercel Remote Cache is convenient if you already live there. Self-hosted S3-compatible caches need TLS, auth, and lifecycle policies. Same rules: signed artifacts, least-privilege tokens, no fork write access.
Local and CI sharing the same cache
Developers on the same remote cache as CI get huge wins — and must not upload outputs built with personal .env overrides. Document which env vars participate in the hash. If a teammate gets a cache hit that embeds someone else’s NEXT_PUBLIC_API_URL, your env list is incomplete. Treat that as a correctness bug. Keep fork PRs on read-only tokens (or no remote write) for public repos.
Remote cache is a force multiplier. Declare every build-affecting env var, lock down write tokens, and never cache secrets as outputs. Then the wall-clock win is earned instead of stolen from correctness.