cheat sheet
Turborepo (turbo)
Day-to-day Turborepo CLI commands and turbo.json config patterns — pipelines, filters, remote cache, prune, and CI integration.
Turborepo (turbo) — CLI and pipeline configuration
What it is
Turborepo is Vercel's monorepo task runner. It schedules package.json scripts across workspace packages with awareness of dependency topology, hashes every input to skip already-completed work, and ships local + remote caches so CI runners and teammates share build artefacts.
The CLI ships as the turbo npm package — a Rust binary distributed via npm. Pair it with pnpm workspaces (or npm/yarn/bun workspaces) for package management; turbo only handles task orchestration.
Install
# In the monorepo root
npm install -D turbo
pnpm add -wD turbo # -w for workspace root
yarn add -DW turbo
bun add -d turbo
# Optional global
npm install -g turbo
# Scaffold a fresh turborepo
npx create-turbo@latest
Output: turbo binary on PATH.
Day-to-day commands
| Command | What it does |
|---|---|
turbo run build | Run the build script across every package, respecting dependsOn ordering. |
turbo run build test lint | Multiple tasks in one invocation; Turbo plans the DAG. |
turbo run dev | Run dev scripts in parallel (with persistent: true config). |
turbo run build --filter=@my/web | Only the named package. |
turbo run build --filter=@my/web... | Package and its deps (upstream). |
turbo run build --filter=...@my/ui | Package and its dependents (downstream). |
turbo run build --filter='[origin/main]' | Only packages changed since main. |
turbo run build --dry | Print the planned DAG without executing. |
turbo run build --force | Bypass cache; run everything. |
turbo prune --scope=@my/web --docker | Output a focused subtree for Docker builds. |
turbo login / turbo link | Set up Vercel Remote Cache (per-developer). |
turbo gen | Run a code generator (templates in turbo/generators/). |
turbo daemon status / stop / restart | Manage the background file-watcher daemon. |
Common scenarios
Build pipeline with cache
// turbo.json
{
"$schema": "https://turborepo.com/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["src/**", "package.json", "tsconfig.json"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"lint": {
"outputs": []
}
}
}
turbo run build
Output (cold):
@my/ui:build: ✓ built dist/ in 1.2s
@my/api:build: ✓ built dist/ in 0.8s
@my/web:build: ✓ built .next/ in 4.1s
Tasks: 3 successful, 3 total
Cached: 0 cached, 3 total
Time: 5.2s
Output (warm):
Tasks: 3 successful, 3 total
Cached: 3 cached, 3 total
Time: 142ms >>> FULL TURBO
Parallel dev mode
{
"tasks": {
"dev": {
"cache": false,
"persistent": true
}
}
}
turbo run dev
Output:
• Packages in scope: api, web, ui
• Running dev in 3 packages
web:dev: ready on http://localhost:3000
api:dev: ready on http://localhost:4000
persistent: true tells Turbo the task is long-running. cache: false skips cache replay. Each package's dev script (vite, next dev, tsx watch, etc.) starts in parallel.
Remote cache setup
turbo login # one-time browser-based login
turbo link # link this repo to a Vercel team
Output:
>>> Opening browser to log in
>>> Success! Turborepo CLI authorized for alice@example.com
✔ Linked to vercel/my-repo
~/.turbo/config.json now holds the token. Subsequent turbo run calls check remote cache first.
In CI:
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: my-team
Self-hosted alternative — the community turborepo-remote-cache proxy supports S3, R2, MinIO.
Filter to changed packages in CI
turbo run build test lint --filter='[origin/main]'
Output:
• Packages changed since origin/main: @my/web, @my/ui
• Running build, test, lint in 2 packages
Tasks: 6 successful, 6 total
Time: 3.1s
Only builds packages touched (directly or via dependencies) by the current branch's diff against main. Pair with actions/checkout fetch-depth: 0.
Pruning for Docker
turbo prune --scope=@my/web --docker
Output:
Generating pruned monorepo for @my/web in ./out
- Added @my/web
- Added @my/ui
- Added @my/utils
Outputs an out/ directory:
out/json/— minimalpackage.jsonper workspace package needed by@my/webout/full/— actual source filesout/pnpm-lock.yaml(or equivalent) — regenerated lockfile
Standard Dockerfile pattern:
FROM node:20-alpine AS pruner
WORKDIR /app
COPY . .
RUN npx turbo prune --scope=@my/web --docker
FROM node:20-alpine AS installer
WORKDIR /app
COPY --from=pruner /app/out/json/ .
RUN pnpm install --frozen-lockfile
COPY --from=pruner /app/out/full/ .
RUN pnpm turbo run build --filter=@my/web
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=installer /app/apps/web/.next/standalone ./
CMD ["node", "server.js"]
Multi-task invocation
turbo run build test lint
Output:
• Packages in scope: api, web, ui
• Running build, test, lint in 3 packages
Tasks: 9 successful, 9 total
Cached: 6 cached, 9 total
Time: 2.7s
Turbo plans the combined DAG:
buildtasks run first (respecting^build)testtasks wait for their package'sbuildlintruns in parallel with everything
--concurrency=4 caps simultaneous work; default 10.
Generators
npx turbo gen
Output:
? What generator would you like to run? › react-package
? Package name: new-pkg
✔ packages/new-pkg created
Interactive prompts driven by templates in turbo/generators/config.ts. Useful for "spin up a new internal package with the standard tsconfig + tsup setup".
Useful flags
| Flag | Purpose |
|---|---|
--filter=<pattern> | Scope to specific packages. Supports ... prefix/postfix for deps/dependents and [ref] for git diffs. |
--concurrency=N | Cap parallel tasks. Default 10. |
--force | Skip cache; rebuild everything. |
--dry / --dry=json | Plan without executing. JSON for tooling. |
--graph / --graph=graph.png | Export task graph (DOT or PNG via graphviz). |
--output-logs=errors-only | Show only failed-task logs. |
--output-logs=hash-only | Show only cache-hit hashes — minimal output. |
--no-daemon | Disable the background watcher daemon (debugging). |
--profile=profile.json | Emit Chrome-tracing profile of the run. |
--summarize (--summarize=output.json) | Write run summary to disk for CI dashboards. |
--continue | Continue running siblings even after one fails (!fail-fast). |
Configuration
turbo.json schema (v2)
{
"$schema": "https://turborepo.com/schema.json",
"globalDependencies": [".env", "tsconfig.base.json"],
"globalEnv": ["NODE_ENV", "CI"],
"globalPassThroughEnv": ["GITHUB_TOKEN"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["src/**", "package.json", "tsconfig.json"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"env": ["NEXT_PUBLIC_API_URL"],
"outputLogs": "new-only"
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**", "tests/**"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"deploy": {
"dependsOn": ["build", "test"],
"cache": false
}
}
}
Key fields:
dependsOn— array of task names.^buildmeans "build of every dependency package"; barebuildmeans "build of this package".inputs— files that affect the cache hash. Narrow these to avoid spurious cache busts.outputs— files/directories the task produces. Cached as the task output.env— env vars that contribute to the cache hash AND are passed to the task.passThroughEnv— env vars passed to the task but NOT hashed.cache—true(default) orfalse. Disable for dev / deploy tasks.persistent— long-running tasks. Cannot have other tasks depend on them.outputLogs—"full","hash-only","new-only","errors-only","none".
Per-package turbo.json overrides
A package can have its own turbo.json that extends the root:
// packages/web/turbo.json
{
"extends": ["//"],
"tasks": {
"build": {
"outputs": [".next/**", "!.next/cache/**", "public/sw.js"]
}
}
}
Global env declarations
Any env var your build reads must be declared, OR the cache will be wrong (stripped from the task env by default in v2).
{
"globalEnv": ["NODE_ENV", "CI", "NEXT_PUBLIC_*"]
}
NEXT_PUBLIC_* wildcards match any var with that prefix.
Common pitfalls
- Missing
inputsdeclaration — tsconfig change doesn't invalidate cache. Add tsconfig/biome/eslint configs toglobalDependenciesor per-taskinputs. - Undeclared env var —
process.env.MY_VARisundefinedbecause Turbo strips unknown env. Add toenvorpassThroughEnv. pipelinevstasks(v1 vs v2) —pipelinewas renamed totasksin v2. Runnpx @turbo/codemod migrate.- Remote cache misses on Node version — different Node versions produce different hashes. Pin Node in CI to match dev.
dependsOndirection confusion —^build(with caret) is upstream deps; barebuildis "this package's build first". Reading the docs slowly the first time saves grief.outputs: []for non-output tasks — tests/lint produce no artefacts you want to cache; explicitly setoutputs: [](Turbo will cache "task ran successfully" but no files).- Cache poisoning from PR forks — disable remote cache writes for PRs from forks. Use
TURBO_CACHE_DIR=localfor fork builds. - Daemon hangs —
turbo daemon restartclears stuck state. Common afternode_modulesrebuilds.
See also
- Packages: npm-turbo — full package reference, ecosystem, alternatives
- JavaScript: pnpm — recommended package manager companion
- Packages: npm-concurrently — for single-package script orchestration