GitHub Actions
GitHub's native CI/CD platform. Declarative YAML workflows that automate build, test, deploy, and any development lifecycle task — directly from the repository.
GitHub Actions is the automation platform integrated into GitHub. Every repository can define YAML workflows that execute in response to events — push, PR, schedule, release, or any webhook.
Why choose it
- Native integration — lives inside the repository, no external services to configure
- Marketplace — over 20,000 reusable actions
- Free runners — 2,000 minutes/month on public repos (unlimited), private with plan limits
- Matrix builds — test on multiple OS/versions in parallel
- Secrets management — encrypted variables at repo, environment, and org level
Anatomy of a workflow
# .github/workflows/ci.yml
name: CI # Workflow name
on: # Triggers
push:
branches: [main]
pull_request:
branches: [main]
permissions: # GITHUB_TOKEN permissions
contents: read
env: # Global variables
NODE_VERSION: 20
jobs: # One or more jobs
build:
runs-on: ubuntu-latest # Runner
steps:
- uses: actions/checkout@v4 # Reusable action
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- run: pnpm install # Shell command
- run: pnpm test
- run: pnpm buildMain triggers
on:
# Push and PRs
push:
branches: [main, 'release/**']
paths: ['src/**', 'package.json'] # only if these files change
pull_request:
types: [opened, synchronize, reopened]
# Scheduled (cron UTC)
schedule:
- cron: '0 6 * * 1' # Monday at 6:00 UTC
# Manual from UI or API
workflow_dispatch:
inputs:
environment:
description: 'Deploy target'
required: true
type: choice
options: [staging, production]
# When a release is published
release:
types: [published]
# When another workflow completes
workflow_run:
workflows: [CI]
types: [completed]Jobs and dependencies
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm test
# Deploy only if lint and test pass
deploy:
needs: [lint, test]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: ./deploy.shMatrix strategy
Test on multiple combinations:
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [18, 20, 22]
fail-fast: false # don't cancel others if one fails
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm testSecrets and variables
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # environment with protections
steps:
- run: ./deploy.sh
env:
API_KEY: ${{ secrets.API_KEY }}
DEPLOY_URL: ${{ vars.DEPLOY_URL }}Secret levels:
- Repository — available in all repo workflows
- Environment — require approval and have protection rules
- Organization — shared across org repos
Caching
- uses: actions/cache@v4
with:
path: |
~/.pnpm-store
node_modules
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-Artifacts
Share files between jobs or download later:
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 7
# In another job:
- uses: actions/download-artifact@v4
with:
name: build-outputReusable workflows
Define workflows other repos can call:
# .github/workflows/reusable-deploy.yml
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
deploy_key:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to ${{ inputs.environment }}"# Call from another workflow
jobs:
deploy:
uses: org/repo/.github/workflows/reusable-deploy.yml@main
with:
environment: production
secrets:
deploy_key: ${{ secrets.DEPLOY_KEY }}Composite actions
Create custom actions combining steps:
# .github/actions/setup-project/action.yml
name: Setup Project
description: Install dependencies and build
runs:
using: composite
steps:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
shell: bash
- run: pnpm build
shell: bashSecurity
# Principle of least privilege
permissions:
contents: read
pull-requests: write
# Pin actions by SHA (not by tag)
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
# Limit default GITHUB_TOKEN permissions
# Settings → Actions → General → Workflow permissions → Read repository contentsCosts and limits
| Plan | Minutes/month | Storage |
|---|---|---|
| Free | 2,000 | 500 MB |
| Team | 3,000 | 2 GB |
| Enterprise | 50,000 | 50 GB |
Public repos: unlimited minutes on Linux runners.
Anti-patterns
- Monolithic workflows — a 500-line YAML. Split into reusable workflows.
- No caching — reinstalling dependencies every run wastes minutes.
- Secrets in logs —
echo $SECRETexposes them. GitHub masks but don't trust blindly. - Mutable tags on actions —
@v4can change. Pin by SHA for security. - No timeout — hanging jobs consume minutes. Always define
timeout-minutes.
Why it matters
GitHub Actions eliminated the need to maintain a separate CI server for most projects. By living alongside the code, workflows are versioned, reviewed in PRs, and executed without external configuration. For open source projects, the free minutes make it the default choice.
References
- GitHub Actions Documentation — GitHub, 2024. Complete official documentation.
- GitHub Actions Marketplace — GitHub, 2024. Catalog of reusable actions.
- Security Hardening for GitHub Actions — GitHub, 2024. Official security guide.
- Awesome Actions — Sarah Drasner, 2024. Curated collection of actions by category.
- GitHub Actions: The Full Course — Fireship, 2023. Visual introduction in 100 seconds + tutorial.