GitHub Actions
What is GitHub Actions?
GitHub Actions is a CI/CD (Continuous Integration / Continuous Deployment) platform built directly into GitHub. It lets you automate tasks in response to events in your repository — like running tests whenever code is pushed, or deploying your app when a PR is merged.
You define workflows in YAML files stored in .github/workflows/. GitHub runs them on virtual machines called runners.
Core Concepts
| Term | Description |
|---|---|
| Workflow | An automated process defined in a YAML file |
| Event | What triggers the workflow (push, pull_request, schedule, etc.) |
| Job | A set of steps that run on the same runner |
| Step | An individual task — run a command or use an action |
| Action | A reusable unit of work (from GitHub Marketplace or your own) |
| Runner | The virtual machine that executes the job |
Your First Workflow
Create .github/workflows/ci.yml in your project:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
This workflow runs on every push to main and every PR targeting main. It checks out your code, sets up Node.js, installs dependencies, and runs your test suite.
Common Events
# Any push to any branch
on: push
# Push to specific branches only
on:
push:
branches: [main, develop]
# Multiple events with filters
on:
push:
branches: [main]
pull_request:
branches: [main] # PRs targeting main
schedule:
- cron: "0 9 * * 1" # every Monday at 9am UTC
workflow_dispatch: # manual trigger from GitHub UI
Practical Workflow: Lint + Test + Build
A realistic Node.js project workflow:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20] # test on multiple Node versions
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test -- --coverage
- name: Build
run: npm run build
The matrix strategy runs the same job multiple times with different configurations — here, it tests on both Node 18 and Node 20 simultaneously.
Using Secrets
Never hardcode API keys or passwords in workflows. Store them in GitHub Secrets (Settings → Secrets and variables → Actions) and reference them as environment variables:
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.MY_API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run deploy
Secrets are masked in logs — they'll never appear as plain text.
Caching Dependencies
Speed up workflows by caching node_modules between runs:
- name: Cache node_modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
actions/setup-node with cache: 'npm' does this automatically — you usually don't need to add this separately.
Deploy on Merge
A common pattern: run tests on PRs, deploy when merged to main:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npm run build
- name: Deploy to production
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: npx vercel --prod --token $VERCEL_TOKEN
Workflow Status Badge
Add a status badge to your README so everyone can see the CI status at a glance:

Useful Marketplace Actions
GitHub Marketplace has thousands of pre-built actions. Some popular ones:
| Action | Purpose |
|---|---|
actions/checkout@v4 | Check out repository code |
actions/setup-node@v4 | Set up Node.js |
actions/cache@v4 | Cache files between runs |
actions/upload-artifact@v4 | Save files from a job |
actions/download-artifact@v4 | Download files from another job |
Viewing Workflow Runs
Go to your repository on GitHub → Actions tab. You can see:
- All past and running workflows
- Logs for each step
- How long each job took
- Which jobs failed and why
Click on a failed job to see the exact error output.
Quick Troubleshooting
| Problem | Fix |
|---|---|
| Workflow doesn't trigger | Check the on: event and branch name matches exactly |
npm ci fails | Ensure package-lock.json is committed |
| Secret not found | Verify the secret name matches exactly (case-sensitive) |
| Job takes too long | Add dependency caching |
| Tests pass locally but fail in CI | Check environment differences (Node version, OS, env vars) |
Start simple — a workflow that just runs npm test on every PR is already a massive improvement over manual testing. Add complexity (deploy, notifications, etc.) only once the basics are solid.