cheat sheet

Turborepo (turbo)

Day-to-day Turborepo CLI commands and turbo.json config patterns — pipelines, filters, remote cache, prune, and CI integration.

#turbo#cli#monorepo#buildupdated 05-31-2026

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

bash
# 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

CommandWhat it does
turbo run buildRun the build script across every package, respecting dependsOn ordering.
turbo run build test lintMultiple tasks in one invocation; Turbo plans the DAG.
turbo run devRun dev scripts in parallel (with persistent: true config).
turbo run build --filter=@my/webOnly the named package.
turbo run build --filter=@my/web...Package and its deps (upstream).
turbo run build --filter=...@my/uiPackage and its dependents (downstream).
turbo run build --filter='[origin/main]'Only packages changed since main.
turbo run build --dryPrint the planned DAG without executing.
turbo run build --forceBypass cache; run everything.
turbo prune --scope=@my/web --dockerOutput a focused subtree for Docker builds.
turbo login / turbo linkSet up Vercel Remote Cache (per-developer).
turbo genRun a code generator (templates in turbo/generators/).
turbo daemon status / stop / restartManage the background file-watcher daemon.

Common scenarios

Build pipeline with cache

jsonc
// 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": []
    }
  }
}
bash
turbo run build

Output (cold):

text
@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):

text
Tasks:  3 successful, 3 total
Cached: 3 cached, 3 total
Time:   142ms >>> FULL TURBO

Parallel dev mode

jsonc
{
  "tasks": {
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
bash
turbo run dev

Output:

text
• 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

bash
turbo login                   # one-time browser-based login
turbo link                    # link this repo to a Vercel team

Output:

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

yaml
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

bash
turbo run build test lint --filter='[origin/main]'

Output:

text
• 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

bash
turbo prune --scope=@my/web --docker

Output:

text
Generating pruned monorepo for @my/web in ./out
 - Added @my/web
 - Added @my/ui
 - Added @my/utils

Outputs an out/ directory:

  • out/json/ — minimal package.json per workspace package needed by @my/web
  • out/full/ — actual source files
  • out/pnpm-lock.yaml (or equivalent) — regenerated lockfile

Standard Dockerfile pattern:

dockerfile
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

bash
turbo run build test lint

Output:

text
• 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:

  • build tasks run first (respecting ^build)
  • test tasks wait for their package's build
  • lint runs in parallel with everything

--concurrency=4 caps simultaneous work; default 10.

Generators

bash
npx turbo gen

Output:

text
? 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

FlagPurpose
--filter=<pattern>Scope to specific packages. Supports ... prefix/postfix for deps/dependents and [ref] for git diffs.
--concurrency=NCap parallel tasks. Default 10.
--forceSkip cache; rebuild everything.
--dry / --dry=jsonPlan without executing. JSON for tooling.
--graph / --graph=graph.pngExport task graph (DOT or PNG via graphviz).
--output-logs=errors-onlyShow only failed-task logs.
--output-logs=hash-onlyShow only cache-hit hashes — minimal output.
--no-daemonDisable the background watcher daemon (debugging).
--profile=profile.jsonEmit Chrome-tracing profile of the run.
--summarize (--summarize=output.json)Write run summary to disk for CI dashboards.
--continueContinue running siblings even after one fails (!fail-fast).

Configuration

turbo.json schema (v2)

jsonc
{
  "$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. ^build means "build of every dependency package"; bare build means "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.
  • cachetrue (default) or false. 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:

jsonc
// 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).

jsonc
{
  "globalEnv": ["NODE_ENV", "CI", "NEXT_PUBLIC_*"]
}

NEXT_PUBLIC_* wildcards match any var with that prefix.

Common pitfalls

  1. Missing inputs declaration — tsconfig change doesn't invalidate cache. Add tsconfig/biome/eslint configs to globalDependencies or per-task inputs.
  2. Undeclared env varprocess.env.MY_VAR is undefined because Turbo strips unknown env. Add to env or passThroughEnv.
  3. pipeline vs tasks (v1 vs v2)pipeline was renamed to tasks in v2. Run npx @turbo/codemod migrate.
  4. Remote cache misses on Node version — different Node versions produce different hashes. Pin Node in CI to match dev.
  5. dependsOn direction confusion^build (with caret) is upstream deps; bare build is "this package's build first". Reading the docs slowly the first time saves grief.
  6. outputs: [] for non-output tasks — tests/lint produce no artefacts you want to cache; explicitly set outputs: [] (Turbo will cache "task ran successfully" but no files).
  7. Cache poisoning from PR forks — disable remote cache writes for PRs from forks. Use TURBO_CACHE_DIR=local for fork builds.
  8. Daemon hangsturbo daemon restart clears stuck state. Common after node_modules rebuilds.

See also