agentsclimarketplace

Github actions templates

Skill ckorhonen/claude-skills/skills/github-actions-templates

A curated collection of skills for Claude Code and Codex — specialized capabilities across development, design, AI, security, and more. Part of the cdd.dev/skills family.

Install
npx -y skills add ckorhonen/claude-skills --skill github-actions-templates

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 10 stars10 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Create production-ready GitHub Actions workflows for automated testing, building, and deploying applications. Use when setting up CI/CD with GitHub Actions, automating development workflows, or creating reusable workflow templates.

SKILL.md

20.8 KB, as published. Nobody here has run it

GitHub Actions Templates

Production-ready GitHub Actions workflow patterns for testing, building, and deploying applications.

Purpose

Create efficient, secure GitHub Actions workflows for continuous integration and deployment across various tech stacks.

When to Use

  • Automate testing and deployment
  • Build Docker images and push to registries
  • Deploy to Kubernetes clusters
  • Run security scans
  • Implement matrix builds for multiple environments

Common Workflow Patterns

Pattern 1: Test Workflow

name: Test

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    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"

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info

Pattern 2: Build and Push Docker Image

name: Build and Push

on:
  push:
    branches: [main]
    tags: ["v*"]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Pattern 3: Deploy to Kubernetes

name: Deploy to Kubernetes

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-west-2

      - name: Update kubeconfig
        run: |
          aws eks update-kubeconfig --name production-cluster --region us-west-2

      - name: Deploy to Kubernetes
        run: |
          kubectl apply -f k8s/
          kubectl rollout status deployment/my-app -n production
          kubectl get services -n production

      - name: Verify deployment
        run: |
          kubectl get pods -n production
          kubectl describe deployment my-app -n production

Pattern 4: Matrix Build

name: Matrix Build

on: [push, pull_request]

jobs:
  build:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.9", "3.10", "3.11", "3.12"]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Run tests
        run: pytest

Workflow Best Practices

  1. Use specific action versions (@v4, not @latest)
  2. Cache dependencies to speed up builds
  3. Use secrets for sensitive data
  4. Implement status checks on PRs
  5. Use matrix builds for multi-version testing
  6. Set appropriate permissions
  7. Use reusable workflows for common patterns
  8. Implement approval gates for production
  9. Add notification steps for failures
  10. Use self-hosted runners for sensitive workloads

Reusable Workflows

# .github/workflows/reusable-test.yml
name: Reusable Test Workflow

on:
  workflow_call:
    inputs:
      node-version:
        required: true
        type: string
    secrets:
      NPM_TOKEN:
        required: true

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm ci
      - run: npm test

Use reusable workflow:

jobs:
  call-test:
    uses: ./.github/workflows/reusable-test.yml
    with:
      node-version: "20.x"
    secrets:
      NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Security Scanning

name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  security:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: "fs"
          scan-ref: "."
          format: "sarif"
          output: "trivy-results.sarif"

      - name: Upload Trivy results to GitHub Security
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: "trivy-results.sarif"

      - name: Run Snyk Security Scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

Deployment with Approvals

name: Deploy to Production

on:
  push:
    tags: ["v*"]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com

    steps:
      - uses: actions/checkout@v4

      - name: Deploy application
        run: |
          echo "Deploying to production..."
          # Deployment commands here

      - name: Notify Slack
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {
              "text": "Deployment to production completed successfully!"
            }

Common Pitfalls

This section documents real failure modes and anti-patterns that cause GitHub Actions workflows to fail silently or unexpectedly.

1. Workflow Syntax Errors

Problem: YAML indentation, quotes, and expression syntax are the most common sources of silent failures.

Common Errors:

Incorrect indentation

# ❌ WRONG - Missing indentation
on: push
jobs:
test:
  runs-on: ubuntu-latest
  steps:
  - run: npm test

# ✅ CORRECT
on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm test

Error message: "unexpected mapping value" or "mapping values are not allowed here" Fix: YAML is 2-space indentation strict. Use a YAML linter like yamllint in your workflow.

Expression syntax confusion

# ❌ WRONG - Using $ without github context
- run: echo ${{ matrix.node-version }}

# ❌ WRONG - Missing quotes on complex expressions
env:
  MY_VAR: ${{ github.event.pull_request.title }}

# ✅ CORRECT
- run: echo "${{ matrix.node-version }}"

# ✅ CORRECT with quotes for strings
env:
  MY_VAR: "${{ github.event.pull_request.title }}"

Error message: "Unable to process template language" or "unrecognized named value" Fix: Always quote expressions that will be used in shell commands or as strings. GitHub Actions requires proper quoting for safe expansion.

Multiline strings without pipe syntax

# ❌ WRONG - Multiline without | or >
- run: echo "Line 1
Line 2"

# ✅ CORRECT using pipe (preserves newlines)
- run: |
  npm install
  npm run build
  npm test

# ✅ CORRECT using >  (folds newlines)
- name: Summary
  run: >
    This is a long
    command that spans
    multiple lines

Error message: "expected '<block end>', but found '\\n'" Fix: Use | for literal blocks (preserves newlines) or > for folded blocks (folds newlines to spaces).


2. Secret Handling Mistakes

Problem: Secrets are the most frequently mishandled element. GitHub Actions has specific rules about when secrets are available.

Critical Mistakes:

Hardcoding secrets in workflow files

# ❌ ABSOLUTELY WRONG - Secret is now in git history
- run: |
  aws s3 sync . s3://my-bucket \
    --aws-access-key-id AKIA1234567890ABCDEF \
    --aws-secret-access-key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Error message: None initially, but this is a critical security vulnerability. Fix: Always use ${{ secrets.SECRET_NAME }} and define secrets in GitHub repository settings.

Accessing secrets in non-secret contexts

# ❌ WRONG - Secrets not available in if: conditionals
if: ${{ secrets.DEPLOY_KEY != '' }}

# ✅ CORRECT - Secrets work in env: blocks at job and step level
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      MESSAGE: ${{ secrets.MY_SECRET }} # ← Works at job level
    steps:
      - name: Deploy
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} # ← Works at step level
        run: |
          # Now DEPLOY_KEY is available here
          ./deploy.sh

Error message: Secret appears as *** (masked) but is actually empty/undefined when used in if: conditions. Fix: Use secrets in env: blocks at job or step level. Never use secrets in if: conditions — they are not available there.

Forgetting to mark outputs as sensitive

# ❌ WRONG - Logs leak credentials
- name: Get credentials
  run: |
    TOKEN=$(curl -s https://api.example.com/token)
    echo "TOKEN=$TOKEN" >> $GITHUB_OUTPUT
    echo "Got token: $TOKEN"  # ← Logged!

# ✅ CORRECT - Mark as secret in action output
- name: Get credentials
  id: creds
  run: |
    TOKEN=$(curl -s https://api.example.com/token)
    echo "token=$TOKEN" >> $GITHUB_OUTPUT  # ← Set output
  # Then reference as:
  # ${{ steps.creds.outputs.token }}

Error message: Credentials appear in plain text in workflow logs. Fix: Use $GITHUB_OUTPUT for sensitive values, and never echo them directly.

Wrong secret context in reusable workflows

# ❌ WRONG - Parent secrets aren't automatically passed
jobs:
  call-workflow:
    uses: ./.github/workflows/deploy.yml
    # secrets: inherit not specified!

# ✅ CORRECT
jobs:
  call-workflow:
    uses: ./.github/workflows/deploy.yml
    secrets: inherit  # ← Pass all parent secrets

Error message: Workflow runs but secrets are undefined in called workflow. Fix: Include secrets: inherit when calling reusable workflows that need secrets.


3. Permission Issues

Problem: GITHUB_TOKEN has limited scopes by default, and permissions are easy to get wrong.

Common Problems:

Insufficient token permissions

# ❌ WRONG - Trying to write packages with default permissions
- name: Push to Docker registry
  run: |
    docker push ghcr.io/${{ github.repository }}:latest
    # Fails with "permission denied" because GITHUB_TOKEN lacks 'packages: write'

# ✅ CORRECT - Explicitly grant permissions
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read        # Read repo code
      packages: write       # Write Docker images
      id-token: write       # For OIDC token exchange
    steps:
    - uses: actions/checkout@v4
    - name: Push to Docker registry
      run: docker push ghcr.io/${{ github.repository }}:latest

Error message: "authentication failed, permission denied" or "Insufficient permissions" from API Fix: Always explicitly declare permissions: at job level with required scopes (contents, packages, id-token, pull-requests, issues, deployments, etc.)

Trying to use GITHUB_TOKEN for operations it can't do

# ❌ WRONG - GITHUB_TOKEN can't trigger other workflows
- name: Trigger deployment
  run: |
    curl -X POST https://api.github.com/repos/${{ github.repository }}/dispatches \
      -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
      -d '{"event_type":"deploy"}'
    # Fails silently - GITHUB_TOKEN lacks workflow permissions

# ✅ CORRECT - Use a PAT (Personal Access Token) or GitHub App token
- name: Trigger deployment
  run: |
    curl -X POST https://api.github.com/repos/${{ github.repository }}/dispatches \
      -H "Authorization: token ${{ secrets.WORKFLOW_PAT }}" \
      -d '{"event_type":"deploy"}'

Error message: "Resource not accessible by integration" (HTTP 403) Fix: GITHUB_TOKEN can't trigger workflows (repository_dispatch). Use a PAT stored in secrets for this.

Cross-repository access failures

# ❌ WRONG - GITHUB_TOKEN only has access to current repo
- name: Clone another repo
  run: |
    git clone https://github.com/other-org/private-repo.git
    # Fails - GITHUB_TOKEN has no access

# ✅ CORRECT - Use a PAT with repo access
- name: Clone another repo
  run: |
    git clone https://x-access-token:${{ secrets.CROSS_REPO_PAT }}@github.com/other-org/private-repo.git

Error message: "fatal: could not read Username for 'https://github.com': terminal prompts disabled" Fix: Use a Personal Access Token (PAT) with repo or read:repo_hook scope for cross-repository access.


4. Matrix Strategy Failures

Problem: Matrix builds are powerful but fail silently when combinations are wrong or when exclude/include are misused.

Common Pitfalls:

Undefined matrix context in steps

# ❌ WRONG - Using matrix variables that don't exist
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        # No 'version' defined here
    steps:
    - run: echo "Version is ${{ matrix.version }}"  # ← Empty!

# ✅ CORRECT - Define all matrix variables used
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        version: ['1.0', '2.0']
    steps:
    - run: echo "Building for ${{ matrix.os }} v${{ matrix.version }}"

Error message: Matrix variable appears empty or undefined in logs. Fix: All variables referenced in steps must be defined in strategy.matrix.

Broken exclude/include syntax

# ❌ WRONG - Invalid exclude syntax
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: ['18', '20']
      exclude:
        - os: windows-latest, node-version: '18'  # ← Comma not allowed here

# ✅ CORRECT - Proper exclude syntax
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: ['18', '20']
      exclude:
        - os: windows-latest
          node-version: '18'

# ✅ Using include for specific combinations
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        include:
          - os: windows-latest
            node-version: '20'  # ← Only windows + node 20

Error message: Workflow fails to parse during validation phase. Fix: exclude and include use YAML list syntax with separate lines for each property.

Cartesian product explosion

# ❌ WRONG - Creates 4 × 3 × 2 × 5 = 120 jobs unexpectedly
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest, linux-arm64]
        node-version: ['16', '18', '20']
        npm-version: ['8', '9']
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
        # This is excessive and wastes CI minutes

# ✅ CORRECT - Use include for specific combinations
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            node-version: '20'
            npm-version: '9'
          - os: macos-latest
            node-version: '20'
            npm-version: '9'
          - os: windows-latest
            node-version: '18'
            npm-version: '8'

Error message: None, but suddenly your CI spend quadruples. Fix: Use include to define specific combinations instead of letting matrix create a Cartesian product.


5. Caching Mistakes

Problem: GitHub Actions caching is conservative—stale caches can persist for months and cause mysterious build failures.

Critical Issues:

Stale cache causing repeated failures

# ❌ WRONG - Cache key doesn't change, stale deps stay cached
- uses: actions/cache@v3
  with:
    path: node_modules
    key: deps-${{ runner.os }} # ← Never changes!
    restore-keys: |
      deps-${{ runner.os }}

# ✅ CORRECT - Include lockfile hash in cache key
- uses: actions/cache@v3
  with:
    path: node_modules
    key: deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      deps-${{ runner.os }}-

Error message: Build passes locally but fails in CI with cryptic module errors. Fix: Always include a hash of your dependency lock file in the cache key: ${{ hashFiles('**/package-lock.json') }} for npm, ${{ hashFiles('**/requirements.txt') }} for pip.

Incorrect cache path for language runtimes

# ❌ WRONG - Caching wrong path, cache misses happen
- name: Setup Node
  uses: actions/setup-node@v4
  with:
    node-version: "20.x"
- uses: actions/cache@v3
  with:
    path: /tmp/node_modules # ← Wrong path!
    key: deps-${{ hashFiles('package-lock.json') }}

# ✅ CORRECT - Let action handle caching, or use right path
- name: Setup Node
  uses: actions/setup-node@v4
  with:
    node-version: "20.x"
    cache: "npm" # ← Let the action handle caching

# OR if manual caching:
- uses: actions/cache@v3
  with:
    path: ~/.npm # ← Correct npm cache location
    key: npm-${{ hashFiles('package-lock.json') }}

Error message: Workflow runs slowly, repeatedly downloading dependencies. Fix: Use the built-in cache: parameter in setup-node@v4, or cache the language runtime's official cache directory (e.g., ~/.npm, ~/.pip-cache).

Cache invalidation race conditions

# ❌ WRONG - Multiple branches writing to same cache
branches:
  - main
  - develop
  - '*'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/cache@v3
      with:
        path: dist
        key: build-${{ hashFiles('package.json') }}
        # develop branch builds with old deps, overwrites cache
        # main branch then gets develop's stale build artifacts

# ✅ CORRECT - Include branch in cache key or read-only on non-main
- uses: actions/cache@v3
  with:
    path: dist
    key: build-${{ github.ref }}-${{ hashFiles('package.json') }}
    restore-keys: |
      build-refs/heads/main-
      build-refs/heads/develop-

Error message: Mysterious test failures after merging (cache was poisoned from other branch). Fix: Include ${{ github.ref }} in cache key to isolate per-branch caches, or use read-only mode on non-main branches.

Docker layer cache not preserved

# ❌ WRONG - Rebuilds every layer despite cache-from
- uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: myimage:latest
    # cache-from: type=gha missing!

# ✅ CORRECT - Use GitHub Actions cache backend
- uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: myimage:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max

Error message: Docker builds are extremely slow despite local caching working fine. Fix: Always include cache-from: type=gha and cache-to: type=gha,mode=max in docker/build-push-action to cache layers across runs.


Debugging Workflows

Useful techniques when workflows fail:

  1. Enable debug logging: Set ACTIONS_STEP_DEBUG=true secret in repository settings to see detailed logs
  2. Check permissions: Review job's permissions: block when API calls fail
  3. Validate YAML: Run yamllint locally on workflow files before pushing
  4. Test matrix locally: Simulate matrix combinations in local test script
  5. Inspect cache: View cache entries in GitHub UI under "Actions" → "Caches"
  6. Review secrets: Confirm all ${{ secrets.NAME }} are defined in repository settings

Related Skills

  • gitlab-ci-patterns - For GitLab CI workflows
  • deployment-pipeline-design - For pipeline architecture
  • secrets-management - For secrets handling

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.