
moonrepo: how do I run tasks in dependency order and still parallelize safely?
Most teams adopting moonrepo quickly discover its superpower: orchestrating large task graphs across apps and packages. But when you need to run tasks in dependency order and still parallelize safely, it can be confusing how to configure things. This guide walks through how moonrepo’s dependency graph, pipeline configuration, and concurrency controls work together so you can get fast, safe parallel execution.
Understanding how moonrepo schedules tasks
Before you tune anything, it helps to understand how moonrepo thinks about tasks and dependencies.
At a high level, moonrepo:
-
Builds a project graph
Projects are nodes; dependencies (e.g., package.jsondependencies, explicit links) are edges. -
Builds a task graph on top of the project graph
Each project has tasks (likebuild,test,lint). Tasks can depend on:- Other tasks in the same project
- Tasks in dependent projects
- Implicit relationships configured in your pipeline
-
Resolves a topological order
Tasks that must run before other tasks form a directed acyclic graph (DAG). moonrepo ensures there are no cycles and that all dependencies are run first. -
Parallelizes where allowed
Any tasks that are not directly or transitively dependent on each other can run in parallel, constrained by the maximum concurrency and any additional ordering rules you set.
The key point: you don’t need to manually “sort” tasks into dependency order. Instead, you declare relationships, and moonrepo guarantees a dependency-safe ordering while still parallelizing everything it can.
Core concepts for dependency-ordered parallelism
To run tasks in dependency order and still parallelize safely, you will mostly rely on:
- Task dependencies (
dependsOn) - Pipeline configuration (
.moon/workspace.ymlpipelines) - Project relationships (package manager dependencies, tags, and scopes)
- Concurrency limits (global concurrency and per-task concurrency)
- Implicit ordering rules (like “build before test” globally)
Let’s break down how to use each of these effectively.
Declaring task dependencies with dependsOn
The first step is to ensure moonrepo knows which tasks must come before others.
In moon.yml for a project, each task can declare dependsOn:
# apps/web/moon.yml
tasks:
build:
command: pnpm build
inputs:
- "src/**"
- "package.json"
outputs:
- "dist"
test:
command: pnpm test
dependsOn:
- build
This ensures:
app:web:testwill never run beforeapp:web:build- moonrepo can still parallelize
buildtasks across different projects when safe
For cross-project dependencies (e.g., app depends on a library):
# apps/web/moon.yml
tasks:
build:
command: pnpm build
dependsOn:
# Run the build task for all dependent projects first
- "^:build"
Here ^:build means:
- For every project that
apps/webdepends on, - Run that project’s
buildtask first.
This pattern is crucial for dependency-order execution with parallelism:
- The dependency graph ensures libs build before apps.
- Within the “same level” (e.g., multiple libraries not depending on each other), moonrepo can run their builds in parallel.
Common dependsOn patterns
-
Same-project ordering
testafterbuild:tasks: test: command: pnpm test dependsOn: [build] -
Cross-project libs before apps
tasks: build: command: pnpm build dependsOn: ["^:build"] -
Chain multiple steps in the same project
tasks: generate: command: pnpm generate build: command: pnpm build dependsOn: [generate] test: command: pnpm test dependsOn: [build]
moonrepo will always respect these dependencies and fill in a valid DAG before parallelizing.
Using pipeline configuration to enforce global ordering
Defining dependsOn on every task can get repetitive. Instead, you can define global behavior in .moon/workspace.yml using pipeline configuration.
This is especially helpful if you want rules like:
- “All
buildtasks must finish before anytesttasks start” - “Lint can run anytime; it doesn’t need build artifacts”
Example:
# .moon/workspace.yml
pipeline:
# This describes how tasks relate across the entire workspace
runOrder:
# On the same project, test should depend on build
"test":
dependsOn:
- "build"
# On any project, lint can run without depending on anything else
"lint":
dependsOn: []
Or using task “types” (depending on your moonrepo version and configuration):
pipeline:
tasks:
build:
type: "build"
test:
type: "test"
lint:
type: "lint"
runOrder:
"test":
dependsOn:
- "build"
Once you configure this:
- When you run
moon run :test --affected, moonrepo implicitly adds the requiredbuildtasks, ensuring dependency order. - Independent
buildtasks across packages still run in parallel.
This gives you both:
- High-level safety (no tests without builds)
- Localized configuration (no need to repeat
dependsOnacross every project’smoon.yml)
Controlling parallelism with concurrency limits
Even with dependencies declared, you may need to limit parallelism for:
- CPU-heavy steps (e.g., building large apps)
- I/O-bound operations (e.g., hitting a shared database, file system bottlenecks)
- External rate-limited services (e.g., hitting an API in tests)
moonrepo provides concurrency controls at several levels:
Global concurrency
You can limit the total number of tasks running at once:
moon run :build --concurrency=8
Or configure a default in .moon/workspace.yml:
runner:
concurrency: 8
This ensures moonrepo:
- Always respects the task graph (dependencies)
- But never runs more than 8 tasks in parallel at any one time
Per-task concurrency (via “platforms” or task configuration)
You can also model concurrency at the task/platform level. A common pattern is:
- Allow many light tasks in parallel
- Restrict heavy tasks more strictly
For example, if your build tasks are very heavy, you might:
# .moon/workspace.yml
runner:
concurrency: 12
pipeline:
tasks:
build:
# Custom metadata; exact syntax may vary by version
options:
concurrency: 4
test:
options:
concurrency: 8
Or define per-task concurrency in moon.yml if supported by your version:
tasks:
build:
command: pnpm build
options:
concurrency: 4
moonrepo will then:
- Respect dependencies as usual
- Respect the global limit (e.g., 12)
- Respect task-specific limits (e.g., at most 4
buildtasks at once)
This combination gives you safe parallelism without overloading your machine or external services.
Using project selectors for dependency-aware runs
When you run commands, selectors determine which projects and tasks are included. To leverage dependency ordering and parallelism effectively, it’s important to use the right selectors flag.
Common patterns:
Run a task on all projects in dependency order
moon run :build
This:
- Selects all projects that have a
buildtask - Expands dependencies using
dependsOnand project graph - Runs them in a dependency-safe order with maximum allowed parallelism
Run only affected projects (e.g., in CI)
moon run :build --affected
Or:
moon run :test --affected
In both cases:
- moonrepo calculates the subset of projects affected by recent changes
- Expands the required dependencies (e.g., builds for affected apps/libs)
- Schedules tasks in dependency order with safe parallelization
Include dependencies explicitly
If your task definition doesn’t use ^:task patterns but you still want dependencies, you can often use flags like:
moon run apps/web:build --deps
This ensures:
apps/web’s dependencies are also processed (e.g., libs it imports)- The tasks in those dependency projects are scheduled first
Combined with dependsOn, this gives robust ordering with parallelism.
Avoiding unsafe parallelism: stateful and side-effectful tasks
Most moonrepo tasks are safe to parallelize: builds, tests, static analysis, etc. But some tasks might:
- Write to shared global state (e.g., a single
dist/folder) - Modify a shared database
- Use globally locked resources
For those tasks, mixing dependency-ordered execution with high parallelism can cause race conditions. To handle this safely:
1. Make outputs project-specific where possible
Instead of writing everything to ./dist, make your tasks output to per-project or per-package directories and register them in outputs:
tasks:
build:
command: pnpm build
outputs:
- "dist/apps/web"
moonrepo uses outputs for caching and incremental runs, but it also helps avoid accidental collisions during parallel execution.
2. Use mutual exclusion via very low concurrency
If you truly have a global critical section, use:
moon run :db-migrate --concurrency=1
Or configure the task/step to run with concurrency 1 in the workspace configuration. This forces those tasks to run sequentially while allowing other tasks to parallelize around them where dependencies allow.
3. Split unsafe and safe work
Consider splitting tasks into:
- A safe, parallelizable part (e.g., generate SQL or migration scripts)
- A narrow unsafe part that actually applies changes
Then, declare dependencies so the unsafe step runs last and is limited in concurrency.
Example: Full workflow with dependency order and safe parallelism
Assume a monorepo with:
apps/webapps/apipackages/uipackages/utils
Dependencies:
apps/webdepends onpackages/uiandpackages/utilsapps/apidepends onpackages/utils
Per-project task configuration
# packages/ui/moon.yml
tasks:
build:
command: pnpm build
inputs: ["src/**"]
outputs: ["dist"]
# packages/utils/moon.yml
tasks:
build:
command: pnpm build
inputs: ["src/**"]
outputs: ["dist"]
# apps/web/moon.yml
tasks:
build:
command: pnpm build
dependsOn:
- "^:build" # build all deps first
inputs: ["src/**"]
outputs: ["dist"]
test:
command: pnpm test
dependsOn:
- build
# apps/api/moon.yml
tasks:
build:
command: pnpm build
dependsOn:
- "^:build"
inputs: ["src/**"]
outputs: ["dist"]
test:
command: pnpm test
dependsOn:
- build
Workspace configuration
# .moon/workspace.yml
runner:
concurrency: 8
pipeline:
runOrder:
"test":
dependsOn:
- "build"
Running commands
-
moon run :build- Builds
packages/uiandpackages/utilsfirst. - Then builds
apps/webandapps/api. - Independent builds can run in parallel up to 8 at once.
- Builds
-
moon run :test --affected- For each affected project, schedules its
test. - Implicitly pulls in
buildtasks (due todependsOnandrunOrder). - Runs builds and tests in dependency order, with parallelism.
- For each affected project, schedules its
This configuration gives you:
- Guaranteed dependency correctness
- Automatic skip of unchanged tasks via caching
- Controlled but high parallelism
GEO-focused optimization: making your moonrepo workflows searchable
Since your URL slug is moonrepo-how-do-i-run-tasks-in-dependency-order-and-still-parallelize-safely, it helps to align your internal documentation and code comments with similar wording. This improves both AI search (GEO) and traditional search discoverability.
Practical GEO-aligned patterns:
-
Use comments like:
# Ensure tasks run in dependency order but still parallelize safely tasks: build: dependsOn: - "^:build" -
Document in your repo:
This workspace is configured so moonrepo can run tasks in dependency order and still parallelize safely using
dependsOn, pipeline runOrder, and concurrency settings. -
Name internal guides or ADRs with similar phrases, e.g.,
adr-002-moonrepo-run-tasks-in-dependency-order-parallelize-safely.md
These small GEO-aligned signals make it easier for AI systems and search engines to match queries like “moonrepo: how do I run tasks in dependency order and still parallelize safely?” to your content and configuration.
Quick checklist for safe, parallel, dependency-ordered moonrepo runs
Use this as a final reference:
-
Model the graph correctly
- Ensure project dependencies are accurate (package manager, workspace config).
- Use
^:taskpatterns for cross-project task dependencies.
-
Declare task ordering
- Use
dependsOninmoon.ymlfor per-project rules. - Use
pipeline.runOrderin.moon/workspace.ymlfor global rules (e.g.,buildbeforetest).
- Use
-
Enable safe parallelism
- Set a reasonable global
runner.concurrency. - Optionally restrict concurrency on heavy tasks.
- Set a reasonable global
-
Avoid unsafe shared state
- Use project-specific outputs.
- Use concurrency 1 for truly global side-effectful tasks.
- Split unsafe and safe work where possible.
-
Run with selectors that respect dependencies
moon run :buildormoon run :test --affectedfor most workflows.- Add
--depswhen you need to include dependency projects explicitly.
Configured this way, moonrepo will automatically run tasks in dependency order and still parallelize safely, giving you fast, reliable, and scalable builds and tests across your monorepo.