GitHub Actions Tutorial — Job Dependencies
Job Dependencies
By default, all jobs run in parallel. The needs: keyword creates dependencies between jobs — making them run sequentially or in controlled patterns like fan-out and fan-in.
Basic Sequential Chain
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building..."
test:
runs-on: ubuntu-latest
needs: build # waits for build
steps:
- run: echo "Testing..."
deploy:
runs-on: ubuntu-latest
needs: test # waits for test
steps:
- run: echo "Deploying..."
┌───────┐ ┌──────┐ ┌────────┐
│ build │ → │ test │ → │ deploy │
└───────┘ └──────┘ └────────┘
Fan-Out Pattern
Multiple jobs depend on a single job:
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building..."
test-unit:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Unit tests..."
test-integration:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Integration tests..."
test-e2e:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "E2E tests..."
┌────────────┐
┌─►│ test-unit │
┌───────┐ │ └────────────┘
│ build │ ───┤ ┌────────────────┐
└───────┘ ├─►│ test-integration│
│ └────────────────┘
│ ┌──────────┐
└─►│ test-e2e │
└──────────┘
After build completes, all three test jobs start in parallel.
Fan-In Pattern
A single job waits for multiple jobs:
jobs:
test-unit:
runs-on: ubuntu-latest
steps:
- run: echo "Unit tests..."
test-integration:
runs-on: ubuntu-latest
steps:
- run: echo "Integration tests..."
deploy:
needs: [test-unit, test-integration] # waits for BOTH
runs-on: ubuntu-latest
steps:
- run: echo "All tests passed — deploying!"
┌────────────┐
│ test-unit │──┐
└────────────┘ │ ┌────────┐
├──►│ deploy │
┌────────────────┐│ └────────┘
│ test-integration├┘
└────────────────┘
Diamond Pattern (Fan-Out + Fan-In)
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building..."
test-unit:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Unit tests..."
test-integration:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Integration tests..."
deploy:
needs: [test-unit, test-integration]
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
┌────────────┐
┌───►│ test-unit │───┐
┌───────┐│ └────────────┘ │ ┌────────┐
│ build │─┤ ├─►│ deploy │
└───────┘│ ┌────────────────┐│ └────────┘
└───►│ test-integration├┘
└────────────────┘
Conditional with needs
Run a job even if a dependency failed:
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: exit 1 # this job fails
cleanup:
needs: test
if: always() # runs even if 'test' failed
runs-on: ubuntu-latest
steps:
- run: echo "Cleaning up..."
report:
needs: test
if: failure() # runs ONLY if 'test' failed
runs-on: ubuntu-latest
steps:
- run: echo "Test failed — sending notification"
| Status Function | Runs When |
|---|---|
success() |
All dependencies succeeded (default) |
failure() |
At least one dependency failed |
always() |
Always, regardless of dependency status |
cancelled() |
Workflow was cancelled |
Accessing Dependency Results
You can check the result of a dependency in if: conditions:
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
deploy:
needs: test
if: needs.test.result == 'success'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
Common Mistakes
| Mistake | Fix |
|---|---|
| Circular dependency | needs cannot be circular (A needs B, B needs A) |
| Job skipped unexpectedly | Default if for needs is success() — add if: always() to run regardless |
| Typo in needs value | The value must match a job ID exactly (case-sensitive) |
Quick Reference
| Pattern | Syntax |
|---|---|
| Sequential | needs: job-id |
| Wait for multiple | needs: [job-a, job-b] |
| Always run | if: always() with needs: |
| Run on failure | if: failure() with needs: |
| Check result | needs.<job-id>.result |
← Previous: GitHub-Hosted Runners
Next: Job Outputs →