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.
GitHub Actions integrates seamlessly with Turborepo's caching. You'll configure a CI pipeline that runs all checks in parallel and caches results across builds for faster feedback.
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
- Turborepo automatically caches in CI
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: 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: 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. Verify caching works
Push another commit to the same PR (without code changes):
echo "# Another commit" >> README.md
git add .
git commit -m "test: verify cache"
git pushSecond run is faster:
Run build, lint, test ✓ 15s (was 2m 34s)
@geniusgarage/ui:test: cache hit, replaying
@geniusgarage/web:build: cache hit, replaying
...
Turborepo cached everything!
For even better caching across machines and team members, use Vercel's remote caching. This is covered in detail in the Remote Caching lesson where you'll add TURBO_TOKEN and TURBO_TEAM to your CI environment variables.
Remote caching allows cache sharing across:
- Different developers on your team
- CI and local development
- Different branches and PRs
Without remote caching, each machine only uses its local cache.
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 hit, replaying
Tasks: 8 successful, 8 total
Cached: 1 cached, 8 total
Time: 2m 15s
Cache behavior in CI:
- First run: All cache misses
- Subsequent runs: Cache hits for unchanged packages
- Different branches: Shared cache if using Turborepo cache action
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
- Verified caching works on subsequent runs
- 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?