GitHub Actions Skill
Purpose
Master GitHub Actions workflow patterns, triggers, optimization, and best practices for CI/CD pipelines.
Workflow Structure
Basic Anatomy
name: Workflow Name # Display name in GitHub UI
on: # Trigger events
push:
branches: [main]
permissions: # Required permissions (use minimal)
contents: read
pull-requests: write
jobs: # One or more jobs
job-id: # Unique job identifier
runs-on: ubuntu-latest # Runner environment
steps: # Sequential steps
- name: Step name
run: command
Common Triggers
Push Events
# Push to any branch
on: push
# Push to specific branches
on:
push:
branches:
- main
- develop
# Push to branches matching pattern
on:
push:
branches:
- 'releases/**'
- 'feature/*'
# Exclude branches
on:
push:
branches-ignore:
- 'docs/**'
Pull Request Events
# Any pull request
on: pull_request
# PR to specific branches
on:
pull_request:
branches: [main]
# PR types
on:
pull_request:
types: [opened, synchronize, reopened, closed]
Manual and Scheduled Triggers
# Manual trigger (workflow_dispatch)
on: workflow_dispatch
# With inputs
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy to'
required: true
type: choice
options:
- dev
- staging
- production
# Scheduled (cron)
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight UTC
- cron: '0 */6 * * *' # Every 6 hours
Optimization Patterns
Dependency Caching
# Automatic npm caching (recommended)
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
# Manual caching (more control)
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
Build Artifact Caching
- name: Cache build
uses: actions/cache@v3
with:
path: build/
key: build-${{ github.sha }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: build-files
path: build/
retention-days: 7
Parallel Jobs
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- run: npm test
build:
runs-on: ubuntu-latest
needs: [lint, test] # Wait for lint and test
steps:
- run: npm run build
Conditional Execution
If Conditions
# Run only on push to main
- name: Deploy
if: github.ref == 'refs/heads/main'
run: npm run deploy
# Run only on pull requests
- name: Preview
if: github.event_name == 'pull_request'
run: npm run preview
# Run only on merged PR
- name: Cleanup
if: github.event.pull_request.merged == true
run: ./cleanup.sh
# Complex conditions
- name: Deploy to prod
if: |
github.ref == 'refs/heads/main' &&
github.event_name == 'push' &&
!contains(github.event.head_commit.message, '[skip ci]')
run: deploy
Environment Variables and Secrets
Using Secrets
env:
# Global environment variables
NODE_VERSION: '18'
API_KEY: ${{ secrets.API_KEY }}
jobs:
deploy:
steps:
- name: Deploy with secrets
env:
# Step-specific environment variables
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
API_URL: ${{ secrets.API_URL }}
run: |
echo "Deploying..."
firebase deploy --token $FIREBASE_TOKEN
GitHub Context Variables
# Common context variables
${{ github.repository }} # owner/repo
${{ github.ref }} # refs/heads/main
${{ github.sha }} # commit SHA
${{ github.event_name }} # push, pull_request, etc.
${{ github.actor }} # User who triggered workflow
${{ github.run_id }} # Unique run ID
# Pull request specific
${{ github.event.pull_request.number }}
${{ github.event.pull_request.title }}
${{ github.head_ref }} # PR source branch
${{ github.base_ref }} # PR target branch
Matrix Builds
Test Multiple Versions
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [16, 18, 20]
steps:
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm test
Matrix with Exclusions
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [16, 18, 20]
exclude:
- os: windows-latest
node: 16 # Skip Windows + Node 16
Common Patterns
React App CI
name: React CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test -- --coverage --watchAll=false
env:
CI: true
- name: Build
run: npm run build
Deployment with Approval
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- run: npm run deploy
Conditional Deployment
jobs:
deploy:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: ./deploy.sh
Performance Optimization
Fast npm Install
# ✅ Use npm ci (faster, more reliable)
- run: npm ci
# ❌ Don't use npm install in CI
- run: npm install # Slower, non-deterministic
Caching Strategy
# Cache node_modules
- uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
# Cache .npm directory (smaller, faster)
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
Skip Unnecessary Runs
# Skip CI for docs-only changes
on:
push:
paths-ignore:
- '**.md'
- 'docs/**'
# Run only for specific paths
on:
push:
paths:
- 'src/**'
- 'package*.json'
Best Practices
✅ Do
- Use
npm cinotnpm install - Cache dependencies
- Set specific Node version
- Use latest action versions (
@v3,@v4) - Set minimal permissions
- Quote strings with special characters
- Use meaningful job and step names
❌ Don't
- Hardcode secrets in workflow files
- Use deprecated actions
- Skip dependency caching
- Use
npm installin CI - Leave complex workflows undocumented
- Ignore workflow failures
Debugging Workflows
# View workflow runs
gh run list --workflow=workflow-name.yml
# View specific run with logs
gh run view RUN_ID --log
# Re-run failed workflow
gh run rerun RUN_ID
# Download logs
gh run download RUN_ID
# Watch workflow in real-time
gh run watch
Common Issues
Issue: Workflow not triggering Solution: Check trigger conditions, branch names, path filters
Issue: npm ci fails
Solution: Ensure package-lock.json is committed, check Node version
Issue: Secrets not available
Solution: Verify secret exists (gh secret list), check spelling
Issue: Slow builds
Solution: Enable caching, use npm ci, parallelize jobs
Project Usage
Music-app workflows:
trunk-based-checks.yml: PR validation, testing, build verificationfirebase-hosting-merge.yml: Production deployment (manual trigger)firebase-hosting-pull-request.yml: PR preview (manual trigger)
All workflows use Node 18, npm caching, and trunk-based development checks.