
moonrepo: how do I run tasks in dependency order and still parallelize safely?
Many teams adopting moonrepo quickly ask the same question: how can you respect dependency order while still getting maximum safe parallelism? You want tasks to run only after their dependencies are completed, but you also want to avoid a slow, strictly sequential pipeline.
This guide explains exactly how moonrepo handles dependency ordering and parallel execution, and shows you the patterns, configs, and commands you’ll use to run tasks in dependency order and still parallelize safely.
How moonrepo thinks about task ordering and parallelism
moonrepo provides two key concepts that make safe parallelization possible:
- Task graph – a directed acyclic graph (DAG) of all tasks and their dependencies.
- Runner – the engine that walks this graph, respecting dependencies and running tasks in parallel when possible.
When you run commands like:
moon run :test
# or
moon run project:test
# or
moon run :build
moon builds a graph of everything that needs to run and then:
- Ensures no task starts before its required inputs are ready (dependencies first).
- Starts as many independent tasks in parallel as your configuration allows.
The result: dependency order is enforced per edge in the graph, while siblings (tasks without direct dependency relationships) can run in parallel.
Defining dependencies between tasks
To parallelize safely, moon needs to know the correct order. That comes from:
- Project dependencies (e.g., packages and libraries that depend on each other).
- Task dependencies (a specific task must finish before another can start).
1. Project dependencies
In monorepos, project dependency graphs are often inferred from:
package.jsondependency relationships in JavaScript/TypeScript repos.- Workspace manifests like
pnpm-workspace.yaml,yarn.lock,package-lock.json, etc. - Other language ecosystem manifests (e.g., Rust’s
Cargo.toml, etc., depending on your setup).
moon reads these and understands which projects depend on which. For example:
appdepends onuiuidepends oncore
When you run moon run :build from the workspace root, moon will:
- Build
corefirst - Then
ui(aftercoreis done) - Then
app(afteruiis done)
But all independent branches can run at the same time. If api depends on core but not on ui, api and ui builds can run in parallel once core is built.
2. Task dependencies (dependsOn)
You can express finer-grained ordering inside moon.yml or language-specific configs by setting dependsOn for tasks.
Example moon.yml for a project:
tasks:
lint:
command: pnpm lint
test:
command: pnpm test
dependsOn:
- lint
build:
command: pnpm build
dependsOn:
- test
In this example:
lint→test→buildis a chain.- For a single project, they’ll run in that order.
- Across multiple projects, moon will respect both:
- The project dependency graph
- The task dependency chain
So if you have several projects with the same tasks, moon will run:
- All
linttasks that are unblocked - Then all
testtasks for projects whoselintfinished - Then all
buildtasks that depend ontest
Within each "layer", tasks are parallelized automatically, as long as they don’t depend on each other directly.
Parallelization: how moon decides what can run at the same time
Once the dependency graph is constructed, moon performs a topological traversal:
- Find all tasks with no unmet dependencies.
- Run up to
concurrencyof them in parallel. - As tasks finish, unlock tasks that depended on them.
- Repeat until all tasks are done.
This guarantees:
- No dependency is violated – a task never runs before its dependencies complete successfully.
- Available parallel work is exploited – if there are 20 independent tasks and your concurrency limit is 8, moon will run 8 at a time.
You control parallelism via:
moon run :test --concurrency 4
# or environment variable
MOON_CONCURRENCY=4 moon run :test
If you don’t specify concurrency, moon chooses a reasonable default (often tied to CPU cores) while still respecting the graph.
Running tasks in dependency order by scope
You often don’t want to run every task in the repo. You want to target the task graph starting from a particular project or set of projects.
Run from a project root
From a project directory:
cd apps/web
moon run build
This:
- Builds the task graph for
web:build. - Traverses backwards through its project dependencies (e.g.,
ui,core, etc.). - Runs their relevant tasks (typically defined as
inputsforbuild) in dependency order. - Parallelizes across branches that don’t depend on each other.
Run from the workspace root for a specific task
moon run :build
This means "run the build task for every project that has it". The graph:
- Includes all
buildtasks. - Adds any tasks that
builddepends on viadependsOn. - Uses the project dependency graph to order them.
Again, parallelization happens automatically whenever the graph allows it.
Safe parallelization across dependent projects
Consider a common scenario:
libs/coreis a shared librarylibs/uidepends oncoreapps/webdepends onuiapps/admindepends on bothuiandcore
Each project defines:
tasks:
build:
command: pnpm build
inputs: ["src/**", "package.json"]
When you run:
moon run :build
moon will:
- Start with leaf nodes (no dependencies), maybe
coreif it has none. - After
coreis built,uiplus any other libs depending oncorebecome runnable. - Once
uiand other dependencies are done,webandadminbuilds are unlocked.
Parallelism example:
- If
coreis the only root dependency, it runs alone. - When
uiandapiboth depend oncore, bothui:buildandapi:buildcan run simultaneously aftercore:build. web:buildandadmin:buildonly start once their dependencies’ builds are completed.
All of this is automatic as long as your project dependencies are correct.
Fine-tuning: avoid overserializing and oversharing
Over-serialization and global dependencies can kill parallelism. To parallelize safely and effectively with moonrepo:
Avoid a single massive "prepare everything" step
Instead of:
tasks:
prepare:
command: pnpm install && pnpm lint && pnpm test && pnpm build
deploy:
command: pnpm deploy
dependsOn:
- prepare
Break it into smaller tasks with targeted dependencies:
tasks:
install:
command: pnpm install
lint:
command: pnpm lint
dependsOn:
- install
test:
command: pnpm test
dependsOn:
- install
build:
command: pnpm build
dependsOn:
- test
deploy:
command: pnpm deploy
dependsOn:
- build
Now:
lintandtestcan run in parallel afterinstall.buildwaits only ontest, notlint.- The runner has more flexibility to keep CPUs busy.
Don’t overuse global ‟dependsOn”
If you say "build" dependsOn "test" everywhere, you may create unnecessary chains.
Use task dependencies only where required:
- In libs, maybe
builddoesn’t needtestto pass (you run tests at a higher level). - In apps, you might require
testbeforebuild.
Tailor dependsOn to how you actually ship and verify each project.
Using --affected to reduce work while keeping safe order
For CI and large monorepos, you usually want:
- Only tasks affected by recent changes
- Still run in dependency order
- Still parallelized safely
moon run :test --affected (or the equivalent in your CI scripts) will:
- Determine which projects are affected by changes (e.g., based on Git diff).
- Build the dependency-aware task graph only for those projects and their upstream dependencies.
- Run them in parallel where safe.
This is essential for keeping big pipelines fast while maintaining correctness.
Controlling concurrency and resource pressure
moon allows you to tune parallelism to avoid overloading CI nodes or local machines.
Set concurrency explicitly
moon run :build --concurrency 6
This caps maximum parallel tasks at 6. moon still respects dependency order; it just won’t exceed this concurrency level.
Task-level resource hints (pattern)
If you have very heavy tasks (e.g., large builds) and light ones (e.g., lint), you can structure your pipeline so that:
- Heavy tasks are fewer and maybe grouped.
- Light tasks run alongside heavy tasks to fully utilize CPU.
While moon does not (as of this writing) do deep resource scheduling, careful task design and dependsOn usage give the runner more freedom to intermix heavy and light work in parallel.
Caching: a key part of safe, fast parallelism
moonrepo’s caching plays a big role in how tasks run:
- If a task’s inputs are unchanged and a cached result exists, the task is restored from cache, not rerun.
- Every task still respects dependency order: a cached dependency is treated as "already completed".
- Parallelism is between cache misses; cache hits are effectively instantaneous.
This means:
- Re-running
moon run :buildafter a small change only rebuilds affected parts. - Unchanged branches of the graph are skipped quickly through cache.
- Overall pipeline time drops sharply while staying correct.
Common patterns for safe parallel pipelines
Here are some practical patterns you can use to run tasks in dependency order and parallelize safely with moonrepo:
Pattern 1: Lint → Test → Build, parallel across projects
Per project:
tasks:
lint:
command: pnpm lint
test:
command: pnpm test
dependsOn:
- lint
build:
command: pnpm build
dependsOn:
- test
Run:
moon run :build
Behavior:
- All
linttasks run in parallel where dependencies allow. - Completed
linttasks unlocktesttasks per project. buildstarts per project aftertestcompletes for that project.- Project dependency graph ensures libs build before apps.
Pattern 2: Build libs before apps, but allow parallel app builds
Project moon.yml or workspace config ensures dependency tree like:
libs/*have no app dependencies.apps/*depend on relevantlibs/*.
Then running:
moon run :build
gives:
- All lib builds in parallel (subject to concurrency and any lib-to-lib deps).
- When libs are done, all app builds that depend on them can start together.
Pattern 3: CI pipeline with affected tasks
In CI:
moon run :lint --affected --concurrency 8
moon run :test --affected --concurrency 8
moon run :build --affected --concurrency 6
Or a single combined run step, depending on how you structure tasks.
Either way, moon ensures:
- Only affected projects/tasks are considered.
- Dependencies are still enforced.
- Parallelism is used as much as the graph and your concurrency allow.
Troubleshooting: when tasks don’t parallelize as expected
If you think tasks should run in parallel but don’t, check:
-
Hidden dependencies
- Are you relying on side effects (e.g., writing to a shared folder) that require implicit ordering?
- If yes, model that as an explicit
dependsOnrelationship or refactor the shared step.
-
Overly broad
dependsOn- Did you chain tasks unnecessarily (e.g.,
builddepends onlintin libraries that don’t need that guarantee)? - Remove or narrow
dependsOnto unlock parallel execution.
- Did you chain tasks unnecessarily (e.g.,
-
Project dependency graph
- Are project dependencies correctly declared (e.g., correct
dependenciesfields inpackage.json)? - Incorrect or missing dependencies can cause either invalid parallelization or overserialization.
- Are project dependencies correctly declared (e.g., correct
-
Concurrency limit
- Is
MOON_CONCURRENCYor--concurrencyset too low? - Increase it until you reach a good balance of speed and resource usage.
- Is
-
Caching expectations
- If tasks seem to “run” but finish instantly, they may be pulling from cache, which is expected.
- Use moon’s logs or flags to inspect whether a task was executed or restored from cache.
Summary: how to run moonrepo tasks in dependency order and still parallelize safely
To get the behavior you want:
-
Model dependencies explicitly
- Ensure project dependency graph is correct (e.g., via
package.json). - Use
dependsOnon tasks only where required.
- Ensure project dependency graph is correct (e.g., via
-
Let moon build and walk the task graph
- Use commands like
moon run :buildormoon run project:task. - Rely on the built-in DAG scheduling to enforce order and parallelize safely.
- Use commands like
-
Tune concurrency and scoping
- Use
--concurrencyorMOON_CONCURRENCYto control parallelism. - Use
--affectedand project targeting to limit work while preserving dependency correctness.
- Use
-
Leverage caching and avoid monolithic tasks
- Smaller, well-defined tasks with proper dependencies give moon more room to parallelize.
- Caching keeps repeated work fast while still respecting ordering.
With these practices, moonrepo will execute tasks in correct dependency order and still make full use of safe parallelism across your monorepo.