Github actions advanced
热门Skills中文cn学习版+教程,提供7000+Skills,集成claude skills (11w+Star) | awesome-openclaw-skills (4w+Star) | ui-ux-pro-max-skill (4w+Star)等10余个热门Skill项目
npx -y skills add lingxling/awesome-skills-cn --skill github-actions-advancedAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Design, debug, and harden GitHub Actions CI/CD workflows, including reusable workflows, matrix builds, self-hosted runners, OIDC authentication, caching, environments, secrets, and release automation.
SKILL.md
31.3 KB, as published. Nobody here has run it
GitHub Actions Advanced Skill
Expert guidance for designing, writing, debugging, and securing production-grade GitHub Actions workflows.
When to Use This Skill
- User mentions GitHub Actions,
.github/workflows, CI/CD pipelines, runners, jobs, steps, or actions - User wants to automate builds, tests, deployments, or releases via GitHub
- User asks about matrix builds, reusable workflows, composite actions, or self-hosted runners
- User needs help with OIDC authentication, caching strategies, or secrets management
- User says "my GitHub pipeline is failing" or "set up CI for my repo"
- User asks about workflow security, hardening, or environment protection rules
When NOT to Use This Skill
- The user is working with GitLab CI/CD → recommend
gitlab-ci-patterns - The user is working with CircleCI, Jenkins, or other CI platforms
- The task is purely about Docker image building without GitHub context → recommend
docker-expert - The task is about Kubernetes deployment configuration → recommend
kubernetes-architect
Step 1: Understand Context Before Responding
When invoked, first gather context:
# Discover existing workflows in the repo
find .github/workflows -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -20
# Check for composite actions
find .github/actions -name "action.yml" 2>/dev/null
# Detect tech stack (influences runner OS, language setup actions)
ls package.json requirements.txt Gemfile go.mod Cargo.toml pom.xml 2>/dev/null
Then adapt recommendations to:
- Existing workflow patterns in the repo
- The tech stack and language runtime
- Whether this is a monorepo or single-project repo
- Whether self-hosted or GitHub-hosted runners are in use
Workflow Structure Reference
name: Workflow Name
on: # Triggers (see Triggers section)
push:
branches: [main]
permissions: # Always declare — principle of least privilege
contents: read
env: # Workflow-level env vars
NODE_VERSION: '20'
concurrency: # Prevent duplicate runs
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # Cancel older runs for same branch
jobs:
job-id:
name: Human-readable name
runs-on: ubuntu-24.04 # Pin OS version — never use -latest in prod
timeout-minutes: 15 # Always set — prevents runaway jobs
environment: production # Links to GitHub Environment (approvals/secrets)
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Step name
run: echo "hello"
Triggers (on:)
Common Patterns
on:
push:
branches: [main, 'release/**']
paths-ignore: ['**.md', 'docs/**'] # Skip docs-only changes
pull_request:
types: [opened, synchronize, reopened]
branches: [main]
workflow_dispatch: # Manual trigger with inputs
inputs:
environment:
description: 'Deploy target'
required: true
type: choice
options: [staging, production]
dry-run:
description: 'Dry run only?'
type: boolean
default: false
schedule:
- cron: '0 2 * * 1' # Monday 2am UTC
workflow_call: # Called by other workflows (reusable)
inputs:
image-tag:
type: string
required: true
secrets:
deploy-token:
required: true
release:
types: [published] # Trigger only on published releases
pull_request_target: # Runs with repo secrets — use with care!
types: [labeled] # Gate with label + author_association check
Security Warning:
pull_request_targetruns with repo secrets. Only use after a maintainer labels the PR. Never check out fork code without explicit sandboxing.
Reusable Workflows
Split large pipelines into composable units stored in .github/workflows/.
Convention: Prefix internal/reusable workflows with _ (e.g., _build.yml).
Caller (.github/workflows/deploy.yml)
jobs:
call-build:
uses: ./.github/workflows/_build.yml # Same-repo reusable
# uses: org/repo/.github/workflows/build.yml@main # Cross-repo
with:
image-tag: ${{ github.sha }}
secrets: inherit # Pass all caller secrets down
call-test:
uses: ./.github/workflows/_test.yml
with:
node-version: '20'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # Explicit secret passing
Reusable Workflow (.github/workflows/_build.yml)
on:
workflow_call:
inputs:
image-tag:
type: string
required: true
push:
type: boolean
default: false
secrets:
registry-token:
required: false
outputs:
digest:
description: "Image digest"
value: ${{ jobs.build.outputs.digest }}
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 20
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- id: build
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
with:
push: ${{ inputs.push }}
tags: myapp:${{ inputs.image-tag }}
Matrix Builds
jobs:
test:
strategy:
fail-fast: false # Don't cancel others if one fails
max-parallel: 4 # Limit concurrent runners
matrix:
os: [ubuntu-24.04, windows-2022, macos-14]
node: ['18', '20', '22']
exclude:
- os: windows-2022
node: '18'
include:
- os: ubuntu-24.04
node: '22'
experimental: true # Custom matrix variable
runs-on: ${{ matrix.os }}
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: ${{ matrix.node }}
cache: 'npm'
- run: npm ci
- run: npm test
continue-on-error: ${{ matrix.experimental == true }}
Dynamic Matrix via Script
jobs:
generate-matrix:
runs-on: ubuntu-24.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- id: set-matrix
run: |
SERVICES=$(find services -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | jq -R -s -c 'split("\n")[:-1]')
printf 'matrix={"service":%s}\n' "$SERVICES" >> "$GITHUB_OUTPUT"
build:
needs: generate-matrix
strategy:
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
runs-on: ubuntu-24.04
steps:
- env:
SERVICE: ${{ matrix.service }}
run: echo "Building $SERVICE"
Caching Strategies
Language Setup Actions (Preferred — No Extra Step Needed)
# Node.js
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '20'
cache: 'npm' # or 'yarn' or 'pnpm'
# Python
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: '3.12'
cache: 'pip'
# Go
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: '1.23'
cache: true
# Java / Gradle / Maven
- uses: actions/setup-java@7a6d8a8234af8eb26422e24052f73b12b0e46a27 # v4.6.0
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven' # or 'gradle'
Manual Cache (Any Tool)
- uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
id: cache-deps
with:
path: |
~/.cache/pip
.venv
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
${{ runner.os }}-pip-
- name: Install deps (only on cache miss)
if: steps.cache-deps.outputs.cache-hit != 'true'
run: pip install -r requirements.txt
Docker Layer Caching
- uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
with:
cache-from: type=gha
cache-to: type=gha,mode=max
# For registry-backed cache (cross-branch):
# cache-from: type=registry,ref=ghcr.io/myorg/myapp:buildcache
# cache-to: type=registry,ref=ghcr.io/myorg/myapp:buildcache,mode=max
OIDC Authentication (Keyless Cloud Auth)
Never store long-lived cloud credentials as secrets. Use OIDC to get short-lived tokens that expire automatically.
AWS
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
role-session-name: GitHubActions-${{ github.run_id }}
# Trust policy on the IAM role must include:
# "token.actions.githubusercontent.com" as OIDC provider
# Condition: "repo:org/repo:ref:refs/heads/main" (restrict to branch)
GCP (Workload Identity Federation)
permissions:
id-token: write
contents: read
steps:
- uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7
with:
workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider
service_account: [email protected]
token_format: access_token # or 'id_token'
Azure (Federated Identity)
permissions:
id-token: write
contents: read
steps:
- uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# No client secret needed! Uses OIDC federated credentials
Environments & Deployment Protection
jobs:
deploy-staging:
environment:
name: staging
url: https://staging.myapp.com
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- run: ./scripts/deploy.sh staging
deploy-production:
needs: deploy-staging
environment:
name: production
url: https://myapp.com # Shown in the GitHub UI deployment panel
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- run: ./scripts/deploy.sh production
Configure in Settings → Environments:
- Required reviewers — manual approval gate before run
- Wait timer — delay after approval (e.g., 10-minute buffer)
- Branch/tag restrictions — only
mainorv*tags can deploy to prod - Environment-specific secrets — override repo-level secrets per environment
- Deployment branches — whitelist which branches can target this environment
Secrets Management
# Access repo/org/environment secrets
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
# Auto-provided token — no setup needed
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# Hierarchy (most specific wins):
# environment secret > repo secret > org secret
Masking Dynamic Values
- name: Generate and mask dynamic token
run: |
TOKEN=$(./scripts/generate-token.sh)
echo "::add-mask::$TOKEN" # Mask in all subsequent logs
echo "DEPLOY_TOKEN=$TOKEN" >> $GITHUB_ENV
Secrets in Composite Actions
# Secrets cannot be passed as inputs to composite actions
# Pass them as env vars instead:
- uses: ./.github/actions/my-action
env:
SECRET_VALUE: ${{ secrets.MY_SECRET }}
Composite Actions
Package reusable step sequences into local actions. No container spin-up, no separate workflow file needed.
Action Definition (.github/actions/setup-app/action.yml)
name: Setup App
description: Install and configure application dependencies
inputs:
node-version:
description: 'Node.js version'
required: false
default: '20'
install-flags:
description: 'Additional npm install flags'
required: false
default: ''
outputs:
cache-hit:
description: 'Whether the dependency cache was hit'
value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: composite
steps:
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: ${{ inputs.node-version }}
cache: npm
- id: cache
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: node_modules
key: ${{ runner.os }}-node-${{ inputs.node-version }}-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
env:
INSTALL_FLAGS: ${{ inputs.install-flags }}
run: |
args=()
case "$INSTALL_FLAGS" in
"") ;;
"--ignore-scripts") args+=(--ignore-scripts) ;;
*) echo "Unsupported install flags" >&2; exit 1 ;;
esac
npm ci "${args[@]}"
- name: Build
shell: bash
run: npm run build
Usage in a Workflow
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/setup-app
with:
node-version: '22'
install-flags: '--ignore-scripts'
Self-Hosted Runners
jobs:
build-gpu:
runs-on: [self-hosted, linux, x64, gpu] # Label matching
timeout-minutes: 60
build-arm:
runs-on: [self-hosted, linux, arm64]
Runner Best Practices
| Practice | Details |
|---|---|
| Ephemeral runners | Use Actions Runner Controller (ARC) on Kubernetes for fresh runners per job |
| Isolation | Never share prod runners with untrusted/fork PR workflows |
| Cleanup hooks | Set ACTIONS_RUNNER_HOOK_JOB_COMPLETED to reset environment |
| Runner groups | Use groups to restrict which repos/workflows can access which runners |
| Labels | Use custom labels (e.g., gpu, high-memory) for precise targeting |
| Security | Disable fork PR access to self-hosted runners in Settings |
# Actions Runner Controller (Kubernetes) — recommended for ephemeral runners
helm install arc \
--namespace arc-systems \
--create-namespace \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
Conditional Execution & Flow Control
# Condition on branch + event
- run: ./scripts/deploy.sh
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# Continue on error (non-blocking steps)
- run: ./scripts/lint.sh
continue-on-error: true
# Job dependency and conditional execution
jobs:
test:
runs-on: ubuntu-24.04
outputs:
result: ${{ steps.run-tests.outcome }}
deploy:
needs: [test, build]
if: |
needs.test.result == 'success' &&
needs.build.result == 'success' &&
github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
notify-failure:
needs: [test, deploy]
if: failure() # Runs even if earlier jobs fail
runs-on: ubuntu-24.04
steps:
- run: ./scripts/notify-slack.sh "Pipeline failed!"
Passing Data Between Jobs
jobs:
prepare:
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.get-version.outputs.version }}
should-deploy: ${{ steps.check.outputs.deploy }}
steps:
- id: get-version
run: |
VERSION=$(tr -d '\r\n' < VERSION)
case "$VERSION" in
""|*[!0-9A-Za-z._-]*) echo "Invalid VERSION" >&2; exit 1 ;;
esac
printf 'version=%s\n' "$VERSION" >> "$GITHUB_OUTPUT"
- id: check
run: |
if git log -1 --pretty=%B | grep -q '\[deploy\]'; then
echo "deploy=true" >> $GITHUB_OUTPUT
else
echo "deploy=false" >> $GITHUB_OUTPUT
fi
build:
needs: prepare
if: needs.prepare.outputs.should-deploy == 'true'
runs-on: ubuntu-24.04
steps:
- env:
VERSION: ${{ needs.prepare.outputs.version }}
run: echo "Building version $VERSION"
Security Hardening
1. Always Declare Permissions (Least Privilege)
# Workflow-level default — restrict everything
permissions:
contents: read
jobs:
publish:
# Job-level override — only expand what's needed
permissions:
contents: write # Only for release/publish jobs
packages: write # Only for container push jobs
pull-requests: write # Only for PR comment jobs
id-token: write # Only for OIDC auth jobs
2. Pin Third-Party Actions to Full Commit SHA
# ❌ UNSAFE — tag can be mutated or hijacked
- uses: actions/checkout@v4
# ✅ SAFE — commit SHA is immutable
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Tool to automate SHA pinning:
# npx pin-github-action .github/workflows/*.yml
# or: pip install ratchet && ratchet pin .github/workflows/
3. Prevent Script Injection
# ❌ UNSAFE — attacker controls PR title, which gets expanded in shell
- run: echo "${{ github.event.pull_request.title }}"
# ✅ SAFE — pass through environment variable (shell doesn't evaluate it)
- env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "$PR_TITLE"
# ✅ SAFE — expressions in if: conditions are evaluated by Actions, not shell
- if: github.event.pull_request.draft == false
run: echo "Not a draft"
Never place ${{ ... }} directly inside run: when the value can come from
PR metadata, workflow inputs, repository files, matrix JSON, or earlier job
outputs. Put it in env: first, validate allowlisted values where possible, and
reference the shell variable with quotes.
4. Restrict pull_request_target Usage
# Only run when a maintainer adds a specific label — prevents untrusted execution
on:
pull_request_target:
types: [labeled]
jobs:
validate:
# Double-guard: check label name AND author_association
if: |
github.event.label.name == 'safe-to-test' &&
(github.event.pull_request.author_association == 'COLLABORATOR' ||
github.event.pull_request.author_association == 'MEMBER' ||
github.event.pull_request.author_association == 'OWNER')
5. Harden with StepSecurity
# Add to every workflow — hardens runner, monitors outbound traffic
- uses: step-security/harden-runner@4d991eb9995541a0b71d1b66f1f98a5f1bef422c # v2.11.0
with:
egress-policy: audit # Start with 'audit', move to 'block' after confirming allowlist
allowed-endpoints: >
api.github.com:443
registry.npmjs.org:443
objects.githubusercontent.com:443
Debugging Techniques
# Enable runner diagnostic logging via repo secrets:
# ACTIONS_RUNNER_DEBUG = true
# ACTIONS_STEP_DEBUG = true
# Dump full GitHub context for inspection
- name: Debug — dump github context
if: runner.debug == '1'
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT" | jq '.'
# Dump all available contexts
- name: Debug — dump all contexts
if: runner.debug == '1'
run: |
echo "github: ${{ toJson(github) }}"
echo "env: ${{ toJson(env) }}"
echo "vars: ${{ toJson(vars) }}"
echo "runner: ${{ toJson(runner) }}"
# SSH into a failing runner for interactive debugging
- uses: mxschmitt/action-tmate@7b04f3521e6b0a9fc56fa8f9f50da4bcfb5fc7b5 # v3.19.0
if: failure() && runner.debug == '1'
with:
limit-access-to-actor: true # Only the workflow triggerer can SSH in
timeout-minutes: 30
# Check what's pre-installed on GitHub-hosted runners
- run: |
echo "=== Tool Versions ==="
node --version
python3 --version
go version
docker --version
echo "=== Disk Space ==="
df -h
echo "=== Memory ==="
free -h
Complete Pipeline Patterns
Pattern 1: Build → Test → Push → Deploy
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
permissions:
contents: read
jobs:
# ── Build & Test ──────────────────────────────────────
build-test:
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
checks: write # For test result reporting
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run test -- --coverage
- run: npm run build
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: build-artifacts
path: dist/
retention-days: 7
# ── Push Image (main branch only) ─────────────────────
push-image:
needs: build-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
packages: write
id-token: write # For OIDC
outputs:
image-digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
- uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@70b2cdc6480c1a8b86edf1777157f8f437de2166 # v5.5.1
id: meta
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=sha,format=long
type=raw,value=latest
- id: push
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true # SLSA provenance attestation
sbom: true # Software Bill of Materials
# ── Deploy Staging ────────────────────────────────────
deploy-staging:
needs: push-image
runs-on: ubuntu-24.04
timeout-minutes: 30
environment:
name: staging
url: https://staging.myapp.com
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- env:
IMAGE_DIGEST: ${{ needs.push-image.outputs.image-digest }}
run: ./scripts/deploy.sh staging "$IMAGE_DIGEST"
# ── Deploy Production (manual approval required) ──────
deploy-production:
needs: deploy-staging
runs-on: ubuntu-24.04
timeout-minutes: 30
environment:
name: production
url: https://myapp.com
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- env:
IMAGE_DIGEST: ${{ needs.push-image.outputs.image-digest }}
run: ./scripts/deploy.sh production "$IMAGE_DIGEST"
Pattern 2: Automated Release with Changelog
name: Release
on:
push:
tags: ['v[0-9]+.[0-9]+.[0-9]+']
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0 # Full history needed for changelog generation
- uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 # v2.0.9
with:
generate_release_notes: true # Auto-generates from PR titles and commits
make_latest: true
fail_on_unmatched_files: true
files: |
dist/**/*.tar.gz
dist/**/*.zip
Pattern 3: Dependency Auto-Update with PR
name: Dependency Updates
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9am UTC
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
update-deps:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '20'
- run: npx npm-check-updates -u
- run: npm install
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
commit-message: 'chore: update npm dependencies'
title: 'chore: update npm dependencies'
branch: 'chore/npm-updates'
delete-branch: true
body: |
Automated dependency updates generated by the dependency update workflow.
Please review and test before merging.
Pattern 4: Security Scanning Pipeline
name: Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * *' # Daily at 6am UTC
permissions:
contents: read
security-events: write # For uploading SARIF results
jobs:
codeql:
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
security-events: write
actions: read
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: github/codeql-action/init@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
with:
languages: javascript-typescript
- uses: github/codeql-action/autobuild@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
- uses: github/codeql-action/analyze@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
container-scan:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8 # v0.28.0
with:
scan-type: 'fs'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- uses: github/codeql-action/upload-sarif@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
with:
sarif_file: 'trivy-results.sarif'
Common Pitfalls & Fixes
| Problem | Cause | Fix |
|---|---|---|
| Workflow doesn't trigger on PR from fork | Fork PRs use restricted GITHUB_TOKEN | Use pull_request not pull_request_target; avoid repo secrets in fork context |
Secret is *** in logs but exposed | Dynamic value not masked | Use echo "::add-mask::$VALUE" before using it |
| Cache never hits across branches | Cache key too specific | Add restore-keys fallback without branch or hash segment |
| Matrix job fails silently | fail-fast: true (default) cancels siblings | Set fail-fast: false during debugging |
| Job hangs indefinitely | No timeout-minutes set | Always set timeout-minutes on every job |
$GITHUB_OUTPUT not set | Old set-output command used | Use echo "key=value" >> $GITHUB_OUTPUT |
| OIDC token request fails | Missing id-token: write permission | Add to job-level permissions block |
| Reusable workflow can't access caller secrets | No secrets: inherit | Add secrets: inherit or explicitly pass secrets |
GitHub Actions Expressions Reference
# Context objects available in expressions
${{ github.sha }} # Commit SHA
${{ github.ref }} # Branch/tag ref
${{ github.ref_name }} # Short branch/tag name
${{ github.event_name }} # Event name (push, pull_request, etc.)
${{ github.actor }} # Username who triggered the run
${{ github.repository }} # org/repo
${{ github.run_id }} # Unique run ID
${{ runner.os }} # Linux, Windows, macOS
# Built-in functions
${{ toJson(github) }} # Serialize context to JSON
${{ fromJson(needs.job.outputs.matrix) }} # Parse JSON string
${{ hashFiles('**/package-lock.json') }} # Hash file(s) for cache keys
${{ format('{0}/{1}', var1, var2) }} # String formatting
${{ join(matrix.items, ',') }} # Join array
# Status functions (use in if: conditions)
${{ success() }} # All previous steps succeeded
${{ failure() }} # Any previous step failed
${{ cancelled() }} # Workflow was cancelled
${{ always() }} # Always runs (success OR failure OR cancelled)
Production Readiness Checklist
Before merging any workflow to main, verify:
Security
- All third-party actions pinned to full commit SHA
-
permissions:declared at workflow and job level (least privilege) - No
${{ }}expressions directly inrun:blocks (use env vars) - OIDC used for cloud credentials (no long-lived secrets stored)
-
pull_request_targetgated with label check + author_association guard - Secrets never echoed or logged
Reliability
-
timeout-minutesset on every job -
fail-fast: falseset for matrix builds used for debugging -
concurrencyconfigured to cancel stale runs - Retry logic for flaky external calls
- Artifact retention policy set appropriately
Performance
- Dependency caching configured (setup-* cache or actions/cache)
- Docker layer caching enabled (
type=gha) - Path filters on
push/pull_requestto skip unrelated changes - Matrix parallelism appropriate (not exhausting runner pool)
Maintainability
- Reusable workflows used for repeated patterns
- Composite actions used for repeated step sequences
- Workflow names and step names are human-readable
-
_prefix on internal/reusable workflow files - Environment protection rules configured for
production
Related Skills
gha-security-review— Deep security audit of existing workflow filesgithub-actions-templates— Copy-paste ready workflow templatesdocker-expert— Container build optimization and Dockerfile best practiceskubernetes-architect— Deploying to Kubernetes from GitHub Actionsgitlab-ci-patterns— GitLab CI/CD equivalent patterns
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Always test reusable workflows in a feature branch before merging to main.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.