Skills github-actions
install
source · Clone the upstream repo
git clone https://github.com/TerminalSkills/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/TerminalSkills/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/github-actions" ~/.claude/skills/terminalskills-skills-github-actions && rm -rf "$T"
manifest:
skills/github-actions/SKILL.mdsource content
GitHub Actions
Build CI/CD pipelines that run on every push, PR, schedule, or manual trigger directly in GitHub.
Workflow Structure
Workflows live in
.github/workflows/. Each YAML file is an independent pipeline.
# .github/workflows/ci.yml name: CI # Display name in GitHub UI on: # Triggers push: branches: [main] pull_request: branches: [main] concurrency: # Cancel duplicate runs group: ci-${{ github.ref }} cancel-in-progress: true jobs: test: # Job ID runs-on: ubuntu-latest # Runner OS steps: - uses: actions/checkout@v4 # Clone repo - uses: actions/setup-node@v4 # Install Node.js with: node-version: 20 cache: npm # Cache npm dependencies - run: npm ci # Install dependencies - run: npm test # Run tests
Triggers
on: # Code events push: branches: [main, develop] paths: ['src/**', 'package.json'] # Only trigger on specific file changes tags: ['v*'] # Tag pushes (releases) pull_request: branches: [main] types: [opened, synchronize, reopened] # Scheduled (cron) schedule: - cron: '0 8 * * 1' # Every Monday at 8:00 UTC # Manual trigger workflow_dispatch: inputs: environment: description: 'Deploy target' required: true type: choice options: [staging, production] # Triggered by other workflows workflow_call: inputs: node-version: type: string default: '20'
Caching
Caching dependencies cuts 30-60 seconds off every run:
# Automatic cache with setup-node - uses: actions/setup-node@v4 with: node-version: 20 cache: npm # Also supports: yarn, pnpm # Manual cache (any directory) - uses: actions/cache@v4 with: path: | node_modules .next/cache key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }} restore-keys: ${{ runner.os }}-deps- # Split save/restore for parallel jobs # Job 1: save - uses: actions/cache/save@v4 with: path: node_modules key: modules-${{ hashFiles('package-lock.json') }} # Job 2+: restore - uses: actions/cache/restore@v4 with: path: node_modules key: modules-${{ hashFiles('package-lock.json') }}
Artifacts
Share build outputs between jobs:
# Upload - uses: actions/upload-artifact@v4 with: name: build path: dist/ retention-days: 1 # Save storage costs # Download (in another job) - uses: actions/download-artifact@v4 with: name: build path: dist/
Matrix Builds
Test across multiple versions/platforms in parallel:
jobs: test: strategy: fail-fast: false # Don't cancel other jobs if one fails matrix: node-version: [18, 20, 22] os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm test
Secrets and Environments
jobs: deploy: runs-on: ubuntu-latest environment: production # Requires approval if configured steps: - run: deploy --token ${{ secrets.DEPLOY_TOKEN }} # Environment-specific secrets - run: echo "Deploying to ${{ vars.API_URL }}"
Set secrets at: Settings → Secrets and variables → Actions.
Job Dependencies and Outputs
jobs: build: runs-on: ubuntu-latest outputs: version: ${{ steps.ver.outputs.version }} steps: - id: ver run: echo "version=$(node -p 'require(\"./package.json\").version')" >> $GITHUB_OUTPUT deploy: needs: build # Runs after build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' # Only on main branch steps: - run: echo "Deploying version ${{ needs.build.outputs.version }}"
Common Patterns
Lint + Test + Build + Deploy
jobs: lint: 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 lint test: 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 test build: needs: [lint, test] # Both must pass runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20, cache: npm } - run: npm ci && npm run build - uses: actions/upload-artifact@v4 with: { name: build, path: dist/ } deploy: needs: build if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: production steps: - uses: actions/download-artifact@v4 with: { name: build, path: dist/ } - run: npx vercel deploy --prod --token ${{ secrets.VERCEL_TOKEN }}
Docker Build and Push
jobs: docker: runs-on: ubuntu-latest permissions: packages: write steps: - uses: actions/checkout@v4 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v5 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max
PR Comment with Results
- uses: actions/github-script@v7 with: script: | await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: '✅ Build succeeded! Preview: https://...' });
Deploy via SSH
- uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SERVER_HOST }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /opt/app git pull origin main npm ci --production npm run build pm2 restart app
Reusable Workflows
Share workflows across repos:
# .github/workflows/shared-ci.yml (in a shared repo) on: workflow_call: inputs: node-version: type: string default: '20' secrets: npm-token: required: false jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: ${{ inputs.node-version }}, cache: npm } - run: npm ci - run: npm test
# .github/workflows/ci.yml (in consuming repo) jobs: ci: uses: org/shared-workflows/.github/workflows/shared-ci.yml@main with: node-version: '20'
Services (Databases in CI)
jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: testdb ports: ['5432:5432'] options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s redis: image: redis:7 ports: ['6379:6379'] env: DATABASE_URL: postgresql://test:test@localhost:5432/testdb REDIS_URL: redis://localhost:6379 steps: - uses: actions/checkout@v4 - run: npm ci - run: npm test
Billing
- Public repos: unlimited minutes, free
- Private repos (free tier): 2,000 minutes/month
- Linux runners: 1x multiplier
- macOS runners: 10x multiplier (use sparingly)
- Windows runners: 2x multiplier
Guidelines
withconcurrency
— always add this to avoid wasting minutes on outdated runscancel-in-progress- Cache aggressively —
for node_modules, build outputs, Docker layers. Saves 30-60s per run.actions/cache
in matrix builds — see all failures, not just the first onefail-fast: false- Don't store secrets in workflow files — use GitHub Secrets. Never
secrets in logs.echo - Pin action versions to SHA —
is more secure thanuses: actions/checkout@abc123
(prevents supply chain attacks)@v4 - Use
for job ordering — parallel by default. Addneeds
only when there's a real dependency.needs - Artifacts expire — set
to the minimum needed. Default 90 days wastes storage.retention-days
— guard deploy jobs to prevent accidental production deploys from PRsif: github.ref == 'refs/heads/main'- Reusable workflows for org-wide standards — extract common CI patterns into a shared repo