Add GitHub Actions CI pipeline
You've been running pnpm build, pnpm test, pnpm lint locally, but production depends on these passing in CI before deployment. You need automated checks that run on every pull request and push to ensure code quality and catch bugs before they ship.
You'll configure a GitHub Actions pipeline that runs all checks in parallel with Turborepo. Caching across runs comes two lessons from now, once Remote Caching gives CI somewhere to keep it.
Outcome
Create GitHub Actions workflow that builds, tests, and lints the monorepo with Turborepo caching.
Fast track
- Create .github/workflows/ci.yml
- Configure pnpm and Node.js setup
- Run build, lint, and test tasks
- Push and verify CI runs on GitHub
Hands-on exercise 7.1
Set up GitHub Actions CI pipeline for the monorepo.
Requirements:
- Create .github/workflows/ci.yml
- Set up pnpm with caching
- Install dependencies with
pnpm install - Run
turbo build lint testin parallel - Test by pushing to GitHub and verify CI runs
Implementation hints:
- Use pnpm/action-setup for pnpm installation
- Use actions/cache for node_modules
- Use actions/setup-node for Node.js
- GitHub runners start empty, so Turborepo's local cache doesn't carry over between runs (GitHub Actions guide)
Create GitHub Actions workflow
Create .github/workflows/ci.yml:
# TODO: Name the workflow "CI"
# TODO: Set up trigger on:
# - push to main branch
# - pull_request (all branches)
# TODO: Create 'build' job that runs on ubuntu-latest with steps:
# 1. checkout code (actions/checkout@v4)
# 2. setup pnpm (pnpm/action-setup@v2 with version 8)
# 3. setup Node.js (actions/setup-node@v4 with node-version 20, cache 'pnpm')
# 4. install dependencies (run: pnpm install)
# 5. run Turborepo tasks (run: pnpm turbo build lint test)Your task: Write the complete workflow file.
Hints:
- Workflow syntax:
name,on,jobs - Each job has
runs-onandsteps - Steps can use
uses(actions) orrun(commands) - actions/setup-node cache: 'pnpm' caches node_modules
Solution
name: CI
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Run build, lint, and test
run: pnpm turbo build lint testWhat this does:
- Triggers on push to main and all PRs
- Sets up pnpm + Node.js with caching
- Installs dependencies once
- Runs all Turborepo tasks in parallel
Try it
1. Commit and push workflow
git add .github/workflows/ci.yml
git commit -m "ci: add GitHub Actions workflow"
git push origin main2. View workflow run
Go to your GitHub repository → Actions tab.
You'll see the CI workflow running with output like:
Setup pnpm ✓ 3s
Setup Node.js ✓ 12s (cache hit)
Install dependencies ✓ 18s
Run build, lint, test ✓ 2m 34s
@geniusgarage/utils:build ✓
@geniusgarage/ui:build ✓
@geniusgarage/ui:test ✓
@geniusgarage/web:build ✓
@geniusgarage/snippet-manager:build ✓
@geniusgarage/web:lint ✓
@geniusgarage/snippet-manager:lint ✓
All tasks run in parallel!
3. Make a PR and see checks
Create a new branch:
git checkout -b test-ci
echo "# Test CI" >> README.md
git add .
git commit -m "test: verify CI pipeline"
git push origin test-ciCreate PR on GitHub. You'll see:
✓ CI / build (pull_request) Successful in 2m 45s
CI runs automatically on every PR!
4. Push again and watch the cache miss
Push another commit to the same PR (without code changes):
echo "# Another commit" >> README.md
git add .
git commit -m "test: second CI run"
git pushYou might expect the second run to fly. It doesn't:
Run build, lint, test ✓ 2m 31s
@geniusgarage/ui:test: cache miss, executing
@geniusgarage/web:build: cache miss, executing
...
This is expected. Every GitHub Actions run starts on a fresh machine with an empty .turbo folder, so there's nothing local to replay. Turborepo's cache is working fine. It just has nowhere to live between runs yet.
CI best practices
1. Run tasks in parallel
# ✅ good - one command, Turborepo parallelizes
run: turbo build lint test
# ❌ bad - sequential steps
run: turbo build
run: turbo lint
run: turbo test2. Cache dependencies
# ✅ good - cache node_modules
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
# ❌ bad - no cache, slow installs
- uses: actions/setup-node@v43. Use specific action versions
# ✅ good - pinned version
uses: actions/checkout@v4
# ❌ bad - unpinned, may break
uses: actions/checkout@latest4. Fail fast
# Turborepo exits on first failure automatically
# No need to configure this manuallyUnderstanding CI output
Turborepo in CI shows:
• Packages in scope: 5 packages
• Running build in 5 packages
• Running lint in 2 packages
• Running test in 1 package
@geniusgarage/ui:test: cache miss, executing
@geniusgarage/web:build: cache miss, executing
@geniusgarage/snippet-manager:build: cache miss, executing
Tasks: 8 successful, 8 total
Cached: 0 cached, 8 total
Time: 2m 15s
Cache behavior in CI:
- Without Remote Caching: every run is all cache misses, because each runner starts empty
- With Remote Caching (lesson 6.3): unchanged packages replay from the shared cache, across runs, branches, and teammates
Commit
git add .github/workflows/ci.yml
git commit -m "ci: optimize GitHub Actions with Turborepo caching"Done-when
Verify CI pipeline works:
- Created .github/workflows/ci.yml
- Configured triggers (push to main, pull_request)
- Set up pnpm with pnpm/action-setup
- Set up Node.js with actions/setup-node
- Configured pnpm cache
- Added install step with pnpm install
- Added turbo step running build, lint, test
- Pushed workflow and saw it run on GitHub
- Created PR and saw CI checks pass
- Saw the second CI run miss the cache, and know why
- Understood parallel task execution in CI
What's Next
CI is running all tasks, but it rebuilds everything even if only one app changed. Next lesson: Filtering and Git-Based Filtering - you'll learn to run tasks only for changed packages using --filter and --filter=[main], dramatically speeding up CI for large PRs.
Was this helpful?