Claude-skills github-actions-templates
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.
git clone https://github.com/ckorhonen/claude-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/ckorhonen/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/github-actions-templates" ~/.claude/skills/ckorhonen-claude-skills-github-actions-templates && rm -rf "$T"
skills/github-actions-templates/SKILL.mdGitHub 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
Reference: See
assets/test-workflow.yml
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
Reference: See
assets/deploy-workflow.yml
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
Reference: See
assets/matrix-build.yml
Workflow Best Practices
- Use specific action versions (@v4, not @latest)
- Cache dependencies to speed up builds
- Use secrets for sensitive data
- Implement status checks on PRs
- Use matrix builds for multi-version testing
- Set appropriate permissions
- Use reusable workflows for common patterns
- Implement approval gates for production
- Add notification steps for failures
- 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:
- Enable debug logging: Set
secret in repository settings to see detailed logsACTIONS_STEP_DEBUG=true - Check permissions: Review job's
block when API calls failpermissions: - Validate YAML: Run
locally on workflow files before pushingyamllint - Test matrix locally: Simulate matrix combinations in local test script
- Inspect cache: View cache entries in GitHub UI under "Actions" → "Caches"
- Review secrets: Confirm all
are defined in repository settings${{ secrets.NAME }}
Reference Files
- Testing workflow templateassets/test-workflow.yml
- Deployment workflow templateassets/deploy-workflow.yml
- Matrix build templateassets/matrix-build.yml
- Common workflow patternsreferences/common-workflows.md
Related Skills
- For GitLab CI workflowsgitlab-ci-patterns
- For pipeline architecturedeployment-pipeline-design
- For secrets handlingsecrets-management