GitHub Actions Skill
This skill helps you create and maintain GitHub Actions workflows for continuous integration and deployment.
When to Use This Skill
-
Setting up CI/CD pipelines
-
Automating tests and builds
-
Configuring deployment workflows
-
Creating release automation
-
Running scheduled jobs
-
Automating dependency updates
-
Setting up code quality checks
Workflow Structure
.github/ ├── workflows/ │ ├── test.yml # Run tests on PR/push │ ├── deploy-staging.yml # Deploy to staging │ ├── deploy-prod.yml # Deploy to production │ ├── release.yml # Create releases │ ├── security.yml # Security audits │ └── cron-jobs.yml # Scheduled tasks ├── actions/ │ └── setup/ # Reusable actions │ └── action.yml └── dependabot.yml # Dependency updates
Basic Workflow
Test Workflow
.github/workflows/test.yml
name: Test
on: push: branches: [main, develop] pull_request: branches: [main, develop]
jobs: test: runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Run linter
run: pnpm biome check .
- name: Type check
run: pnpm tsc --noEmit
- name: Run tests
run: pnpm test
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
Deployment Workflows
Deploy to Staging
.github/workflows/deploy-staging.yml
name: Deploy to Staging
on: push: branches: [develop] workflow_dispatch:
jobs: deploy: runs-on: ubuntu-latest environment: name: staging url: https://staging.sgcarstrends.com
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Run tests
run: pnpm test
- name: Build
run: pnpm build
- name: Deploy API
run: pnpm -F @sgcarstrends/api deploy:staging
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Deploy Web
run: pnpm -F @sgcarstrends/web deploy:staging
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Run migrations
run: pnpm db:migrate
env:
DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
- name: Notify Slack
if: always()
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"text": "Staging deployment ${{ job.status }}"
}
Deploy to Production
.github/workflows/deploy-prod.yml
name: Deploy to Production
on: push: branches: [main] workflow_dispatch: inputs: confirm: description: "Type 'deploy' to confirm" required: true
jobs: confirm: if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: - name: Confirm deployment run: | if [ "${{ github.event.inputs.confirm }}" != "deploy" ]; then echo "Deployment not confirmed" exit 1 fi
deploy: needs: [confirm] if: always() && (needs.confirm.result == 'success' || github.event_name == 'push') runs-on: ubuntu-latest environment: name: production url: https://sgcarstrends.com
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Run tests
run: pnpm test
- name: Run security audit
run: pnpm audit --audit-level=high
- name: Build
run: pnpm build
- name: Deploy API
run: pnpm -F @sgcarstrends/api deploy:prod
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Deploy Web
run: pnpm -F @sgcarstrends/web deploy:prod
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Run migrations
run: pnpm db:migrate
env:
DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }}
- name: Create deployment
uses: chrnorm/deployment-action@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
environment: production
state: success
- name: Notify team
if: always()
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"text": "🚀 Production deployment ${{ job.status }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Production Deployment*\nStatus: ${{ job.status }}\nCommit: ${{ github.sha }}\nAuthor: ${{ github.actor }}"
}
}
]
}
Release Workflow
Automated Release
.github/workflows/release.yml
name: Release
on: push: branches: [main]
permissions: contents: write issues: write pull-requests: write
jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
- name: Get new version
id: version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.version.outputs.version }}
release_name: Release v${{ steps.version.outputs.version }}
draft: false
prerelease: false
Reusable Workflows
Shared Setup Action
.github/actions/setup/action.yml
name: "Setup Project" description: "Setup Node.js, pnpm, and install dependencies"
inputs: node-version: description: "Node.js version" required: false default: "20"
runs: using: "composite" steps: - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: 8
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: "pnpm"
- name: Install dependencies
shell: bash
run: pnpm install --frozen-lockfile
- name: Cache Turbo
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
Use Reusable Workflow
.github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Setup project
uses: ./.github/actions/setup
- name: Run tests
run: pnpm test
Matrix Strategy
Test Multiple Versions
name: Test Matrix
on: [push, pull_request]
jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] node: [18, 20, 21] exclude: - os: windows-latest node: 18
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: "pnpm"
- run: pnpm install
- run: pnpm test
Conditional Execution
Run Jobs Conditionally
name: Deploy
on: push: branches: [main]
jobs: deploy-api: if: contains(github.event.head_commit.message, '[deploy-api]') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: pnpm -F @sgcarstrends/api deploy:prod
deploy-web: if: contains(github.event.head_commit.message, '[deploy-web]') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: pnpm -F @sgcarstrends/web deploy:prod
deploy-all: if: | !contains(github.event.head_commit.message, '[deploy-api]') && !contains(github.event.head_commit.message, '[deploy-web]') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: pnpm deploy:prod
Caching
Cache Dependencies
-
name: Cache pnpm store uses: actions/cache@v3 with: path: ~/.pnpm-store key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-
-
name: Cache Turbo uses: actions/cache@v3 with: path: .turbo key: ${{ runner.os }}-turbo-${{ github.sha }} restore-keys: | ${{ runner.os }}-turbo-
-
name: Cache Next.js uses: actions/cache@v3 with: path: apps/web/.next/cache key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}
Secrets Management
Using Secrets
- name: Deploy env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} DATABASE_URL: ${{ secrets.DATABASE_URL }} REDIS_URL: ${{ secrets.REDIS_URL }} run: pnpm deploy:prod
Environment-Specific Secrets
jobs: deploy: environment: production steps: - name: Deploy env: DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }} run: pnpm deploy:prod
Scheduled Workflows
Cron Jobs
.github/workflows/cron-jobs.yml
name: Scheduled Jobs
on: schedule: # Run every day at 2 AM UTC - cron: "0 2 * * *" workflow_dispatch:
jobs: update-data: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup
- name: Update car data
run: pnpm -F @sgcarstrends/api run-workflow update-car-data
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
LTA_API_KEY: ${{ secrets.LTA_API_KEY }}
cleanup: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup
- name: Clean old data
run: pnpm -F @sgcarstrends/api run-script cleanup-old-data
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Notifications
Slack Notifications
-
name: Notify Slack on success if: success() uses: slackapi/slack-github-action@v1 with: webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | { "text": "✅ Deployment successful", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "Deployment Successful\nCommit: ${{ github.sha }}\nAuthor: ${{ github.actor }}" } } ] }
-
name: Notify Slack on failure if: failure() uses: slackapi/slack-github-action@v1 with: webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | { "text": "❌ Deployment failed", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "Deployment Failed\nCommit: ${{ github.sha }}\nAuthor: ${{ github.actor }}\nWorkflow: ${{ github.workflow }}" } } ] }
Artifacts
Upload and Download
Upload artifacts
- name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: build-output path: | dist/ .next/ retention-days: 7
Download artifacts in another job
- name: Download build artifacts uses: actions/download-artifact@v3 with: name: build-output
Best Practices
- Use Specific Versions
❌ Using latest
- uses: actions/checkout@latest
✅ Using specific version
- uses: actions/checkout@v4
- Pin Action Versions
✅ Good: Pinned to major version
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
✅ Better: Pinned to commit SHA (most secure)
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- Use Concurrency Controls
name: Deploy
on: push: branches: [main]
concurrency: group: deploy-${{ github.ref }} cancel-in-progress: false # Don't cancel in-progress deployments
- Fail Fast
jobs: test: strategy: fail-fast: true # Stop all jobs if one fails matrix: node: [18, 20, 21]
Troubleshooting
Workflow Not Triggering
Issue: Workflow not running
Solution: Check triggers and permissions
on: push: branches: [main] # Ensure branch name matches pull_request: branches: [main]
permissions: contents: read pull-requests: write
Secret Not Found
Issue: Secret not available
Solution: Check secret name and environment
- name: Deploy environment: production # Ensure environment exists env: SECRET: ${{ secrets.MY_SECRET }} # Check secret name
Cache Not Working
Issue: Cache not restoring
Solution: Verify cache key
- uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
Ensure lockfile exists and path is correct
References
-
GitHub Actions Documentation: https://docs.github.com/en/actions
-
Workflow Syntax: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
-
Actions Marketplace: https://github.com/marketplace?type=actions
-
Related files:
-
.github/workflows/
-
Workflow files
-
Root CLAUDE.md - CI/CD guidelines
Best Practices Summary
-
Pin Versions: Use specific action versions
-
Cache Dependencies: Cache pnpm, Turbo, Next.js
-
Parallel Jobs: Run independent jobs in parallel
-
Fail Fast: Stop on first failure in matrix
-
Secrets Management: Use GitHub Secrets for sensitive data
-
Notifications: Alert team on deployment status
-
Reusable Workflows: Share common setup steps
-
Environment Protection: Use environment rules for production