GitHub Actions: Workflow Automation
A comprehensive guide to GitHub Actions — workflow syntax, triggers, the Actions marketplace, matrix builds, secrets management, the Claude Code GitHub Action for AI-powered PR reviews, caching strategies, artifacts, self-hosted runners, reusable workflows, OIDC cloud auth, and concurrency control.
By Jose Nobile | Updated 2026-04-23 | 15 min read
Workflow Syntax and Triggers
Workflows are YAML files in .github/workflows/. Each workflow has a name, trigger conditions (on), and one or more jobs that run on GitHub-hosted or self-hosted runners. Triggers include push, pull_request, schedule (cron), workflow_dispatch (manual), repository_dispatch (API), and dozens of webhook events (issue comments, releases, deployments). A single repository can have unlimited workflows.
Filter triggers precisely: on: push: branches: [main] runs only on pushes to main. on: pull_request: paths: ['src/**'] runs only when source files change. on: schedule: - cron: '0 6 * * 1' runs every Monday at 6 AM UTC. Combine triggers: a workflow can trigger on both push to main and manual dispatch, with different behavior based on the trigger event using github.event_name.
Jobs run in parallel by default. Use needs to create sequential dependencies: deploy: needs: [test, build] waits for both test and build to succeed. Each job runs on a fresh runner instance with no shared state — pass data between jobs via artifacts or job outputs. Concurrency groups (concurrency: { group: deploy-prod, cancel-in-progress: true }) prevent duplicate deployments.
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
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
Actions Marketplace
The GitHub Actions Marketplace hosts thousands of reusable actions for common CI/CD tasks. Instead of scripting Docker builds, Kubernetes deployments, or Slack notifications from scratch, use community or official actions. Pin actions to a specific version (uses: actions/checkout@v4) or SHA (uses: actions/checkout@abc123) for reproducibility and security — never use @main or @latest in production workflows.
Essential official actions: actions/checkout (clone the repo), actions/setup-node (install Node.js with caching), actions/cache (cache dependencies), actions/upload-artifact and actions/download-artifact (share files between jobs), and github/codeql-action (security analysis). Docker actions like docker/build-push-action handle multi-platform image builds with BuildKit and layer caching.
Create custom actions in three ways: JavaScript actions (fastest startup, run directly in Node.js), Docker container actions (any language, isolated environment), and composite actions (reusable sequences of steps defined in YAML). Composite actions are the simplest to create and maintain — they are just workflow steps packaged into a reusable unit with inputs and outputs.
Matrix Builds
Matrix builds run the same job with different combinations of variables. Define a matrix of Node.js versions, operating systems, or any custom variable, and GitHub Actions spawns a job for each combination. This is essential for testing library compatibility across environments: test on Node 18, 20, and 22 across Ubuntu, macOS, and Windows with a single workflow definition.
The strategy.matrix key defines the variables. include adds specific combinations with extra variables, and exclude removes unwanted combinations. Set fail-fast: false to run all matrix combinations even if one fails — useful when you need complete test results across all platforms. max-parallel limits concurrent jobs to avoid overwhelming external services.
For large matrices, use dynamic matrix generation: a setup job generates the matrix as a JSON output, and the test job uses fromJSON(needs.setup.outputs.matrix) to consume it. This pattern enables matrices defined by file contents (e.g., test every package in a monorepo) or external data (e.g., test every supported database version).
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
node: [18, 20, 22]
exclude:
- os: macos-latest
node: 18
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm test
Secrets and Environments
GitHub Secrets store sensitive values (API keys, tokens, credentials) encrypted and only exposed to workflows at runtime. Secrets are available at repository level, organization level, and environment level. Access them in workflows with ${{ secrets.MY_SECRET }}. GitHub automatically redacts secret values from workflow logs, but be careful with commands that might leak them (e.g., echo or debug logging).
Environments define deployment targets (staging, production) with protection rules: required reviewers, wait timers, branch restrictions, and deployment branch policies. Environment secrets are only available to jobs targeting that environment. This provides a strong security boundary — production database credentials are only accessible to the deploy-production job, not to test jobs running on pull requests.
For OIDC authentication (passwordless), GitHub Actions can assume cloud provider roles directly using aws-actions/configure-aws-credentials or google-github-actions/auth with workload identity federation. This eliminates long-lived service account keys stored as secrets. The workflow requests a short-lived token from GitHub's OIDC provider, and the cloud provider validates it against the repository and workflow. This is the gold standard for cloud authentication in CI/CD.
Claude Code GitHub Action
The Claude Code GitHub Action (anthropics/claude-code-action) brings AI-powered code review and automation directly into your GitHub pull request workflow. When a PR is opened or updated, the action runs Claude Code in headless mode, analyzing the diff, checking for bugs, suggesting improvements, and posting review comments — all automatically without human intervention.
Configuration is straightforward: add the action to a workflow triggered on pull_request events, provide your Anthropic API key as a secret, and optionally specify a custom prompt or CLAUDE.md file. The action reads the PR diff, understands the context of the changes, and posts inline comments on specific lines or a summary review comment. It can approve, request changes, or leave neutral comments based on the findings.
Advanced use cases include: automated code review with project-specific standards (by pointing to a CLAUDE.md file), security audit of PR changes, documentation generation for new functions, test suggestion for uncovered code paths, and automated PR description writing. The action supports custom prompts, allowing teams to enforce specific review criteria like "check for SQL injection" or "verify error handling in all new endpoints."
on:
pull_request:
types: [opened, synchronize]
issue_comment:
types: [created]
jobs:
review:
if: github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '@claude'))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
steps:
- uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-sonnet-4-20250514
The Claude Code GitHub Action is particularly powerful when combined with a project-specific CLAUDE.md. The file defines coding standards, architecture patterns, and review criteria that Claude follows during automated reviews. This creates a consistent, tireless reviewer that catches issues human reviewers often miss — like forgotten error handling, inconsistent naming, or security anti-patterns.
Caching Strategies
Caching dependencies is the single biggest workflow speed optimization. The actions/cache action stores and restores directories between workflow runs. For Node.js, cache ~/.npm (the npm cache directory) keyed on package-lock.json hash. For Python, cache ~/.cache/pip. For Go, cache ~/go/pkg/mod. A cache hit saves 30-120 seconds per job by skipping dependency download and extraction.
Setup actions often include built-in caching: actions/setup-node@v4 with cache: npm automatically caches and restores the global npm cache (~/.npm) without separate cache configuration. This is simpler and recommended over manual actions/cache for standard setups. For complex caching (multiple package managers, monorepo workspace caches), use actions/cache directly with custom keys and restore-keys for fallback matching.
Cache limits: GitHub provides 10GB of cache storage per repository. Caches are evicted LRU (least recently used) when the limit is reached. Branch-specific caches are preferred (the cache from a feature branch is available to its PR but not to other branches) with fallback to the default branch cache. Use restore-keys to define fallback patterns: a partial match on the key prefix restores the most recent cache with that prefix, even if the exact key does not match.
Artifacts
Artifacts are files produced by a job and persisted after it completes. Use actions/upload-artifact@v4 to save build outputs, test reports, or coverage data, and actions/download-artifact@v4 to retrieve them in downstream jobs. Unlike caching (which optimizes repeated dependency installs), artifacts are designed for passing unique build outputs between jobs and making them available for download from the workflow run UI.
Common artifact patterns: a build job uploads compiled binaries, and a deploy job downloads them. A test job uploads JUnit XML reports, and GitHub renders them in the workflow summary. Set retention-days to control how long artifacts are stored (default 90 days, max 400). For monorepos, use distinct artifact names per package to avoid collisions when multiple matrix jobs upload simultaneously.
Artifacts have a 10GB per-repository storage limit and a 500MB single-file upload limit. For large artifacts, compress before uploading. In multi-job pipelines, artifacts are the primary mechanism for sharing state: the test job produces a coverage report artifact, the build job produces a Docker image digest artifact, and the deploy job consumes both to create a deployment with verified test coverage and the exact image hash.
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: dist
- run: npx wrangler pages deploy dist/
Self-Hosted Runners
GitHub-hosted runners provide Ubuntu, macOS, and Windows environments with pre-installed tools. They are ephemeral (destroyed after each job), secure (isolated VMs), and require zero maintenance. However, they have limitations: fixed hardware specs (7GB RAM, 2 CPU for Linux), limited software customization, and no persistent storage between jobs. Self-hosted runners solve these limitations.
Self-hosted runners are machines you manage (physical, VM, or container) that run GitHub Actions jobs. Advantages: custom hardware (GPU, ARM, high-memory), persistent caches, access to private networks (databases, internal APIs), and no per-minute billing. Disadvantages: you manage security (patch the OS, isolate jobs), availability (keep the runner online), and scaling (spin up more runners for demand). Use actions-runner-controller on Kubernetes for auto-scaling self-hosted runners.
For most projects, GitHub-hosted runners are sufficient and recommended for security. Use self-hosted runners only when you need specific hardware (GPU for ML training, ARM for cross-compilation), network access (deploy to an on-premise cluster), or large builds that exceed the 7GB memory limit. Never use self-hosted runners for public repositories — anyone who can open a PR can run arbitrary code on your runner.
Advanced Patterns
Reusable workflows (workflow_call trigger) let you define a workflow once and call it from other workflows, similar to GitLab's shared templates. The called workflow runs in the context of the caller, with inputs and secrets passed explicitly. This is the primary mechanism for standardizing CI/CD across repositories in a GitHub organization.
Composite actions bundle multiple steps into a single reusable action with defined inputs and outputs. Unlike reusable workflows (which are entire workflow files), composite actions are steps that can be mixed with other steps in any job. They are ideal for abstracting repetitive step sequences: "setup environment, authenticate, deploy" becomes uses: ./.github/actions/deploy.
Workflow dispatch with inputs creates parameterized manual runs: select the environment, choose the version, toggle feature flags — all from the GitHub UI. Combined with github.event.inputs, this enables one-click deployments, database migrations, and maintenance operations. At scale, a deployment workflow with environment and version inputs replaces complex deployment scripts with a single button click.
Reusable Workflows
Define CI/CD once, call from any repo. Pass inputs and secrets explicitly. Centralize build, test, and deploy patterns across an organization. The GitHub equivalent of GitLab's shared templates.
Path Filtering
Skip irrelevant jobs with paths and paths-ignore filters. Only run frontend tests when src/frontend/** changes. Saves minutes of CI time and GitHub Actions billing on every push.
OIDC Cloud Auth
Authenticate to AWS, GCP, or Azure without long-lived credentials. GitHub's OIDC provider issues short-lived tokens verified by the cloud. Zero secrets to rotate, zero keys to leak.
Real-World: Production Workflows
GitHub Actions workflows power the CI/CD for this website (josenobile.co) and several open-source and client projects. The workflows demonstrate best practices for testing, building, deploying static sites, running AI-powered code reviews, and maintaining project health with scheduled jobs.
Claude Code PR Review
Every PR triggers Claude Code review via the GitHub Action. Claude analyzes the diff against CLAUDE.md standards, posts inline review comments, and catches bugs, security issues, and style violations before human review.
Deploy on Push
Push to main triggers automatic deployment to Cloudflare Pages. Build step validates HTML, optimizes images, and generates sitemaps. Deployment completes in under 60 seconds with zero-downtime rollout.
Scheduled Health Checks
Cron-triggered workflows run Lighthouse audits, link checks, and certificate expiry monitoring weekly. Results are posted as GitHub Issues when thresholds are violated, creating an automated maintenance system.
Latest GitHub Actions Features (2025-2026)
ARM64 Hosted Runners (GA): GitHub now offers free ARM64 hosted runners with the ubuntu-24.04-arm and ubuntu-22.04-arm labels for public repositories. ARM runners deliver up to 40% better performance for compatible workloads at no additional cost. This is particularly impactful for building multi-architecture Docker images (amd64 + arm64 natively instead of QEMU emulation), running ARM-native tests, and reducing CI costs for organizations deploying to ARM infrastructure (AWS Graviton, GKE Arm nodes). Use runs-on: ubuntu-24.04-arm to opt in.
Artifact Attestations: GitHub Actions now supports cryptographic artifact attestations that link build artifacts to their source code and build process. When you build a binary, container image, or any artifact, the attestation proves it was built from a specific commit in your repository by a specific workflow run. This provides supply chain security by making it verifiable that artifacts have not been tampered with between build and deployment. Attestations follow the in-toto framework and integrate with SLSA provenance standards.
Immutable Actions: A security feature that ensures actions are untampered between publication and consumption. Immutable actions are cryptographically signed and verified at runtime, preventing supply chain attacks where a malicious actor modifies a published action. Combined with artifact attestations, this creates an end-to-end verified pipeline where both the CI/CD configuration (actions) and the build outputs (artifacts) are cryptographically verified.
Runner Scale Set Client (Preview): A standalone Go module for implementing custom autoscaling of self-hosted runners without requiring Kubernetes. Previously, autoscaling runners required the actions-runner-controller on Kubernetes. The new Runner Scale Set Client provides the same scale-set semantics as a Go library, enabling teams to build custom autoscaling solutions on any infrastructure -- VMs, serverless functions, or proprietary orchestrators. This decouples runner autoscaling from Kubernetes dependency.
Copilot Agent Runner Controls (April 2026): GitHub Copilot's cloud coding agent now runs on GitHub Actions infrastructure, and organizations can set a default runner (including large runners or self-hosted runners) for the Copilot agent across all repositories without per-repo configuration. Runner settings can be locked at the org level to prevent overrides. Additionally, Windows ARM64 hosted runners are now available for GitHub Actions, bringing native CI/CD support for Arm64 Windows applications without emulation. GitHub also reduced hosted-runner prices by up to 39% effective January 2026, making CI/CD significantly more affordable at scale.
Service Container Overrides, OIDC Custom Claims, and Azure Failover (April 2026): Service containers in workflow jobs now support entrypoint and command overrides, enabling custom initialization scripts and startup commands for sidecar services like databases and message brokers without building custom images. OIDC custom properties as claims is now GA — organizations can inject repository custom properties into OIDC tokens, allowing cloud providers to scope permissions based on team, environment, or any custom metadata. Azure private networking for hosted runners now includes automatic failover, maintaining secure connectivity to Azure VNETs even during infrastructure transitions.
ARM64 Hosted Runners
Free ubuntu-24.04-arm and ubuntu-22.04-arm labels. Up to 40% performance increase. Native ARM builds without QEMU emulation.
Artifact Attestations
Cryptographic linking of artifacts to source code and build process. SLSA provenance. Verifiable, untampered build outputs.
Immutable Actions
Cryptographically signed actions verified at runtime. Prevents supply chain attacks on published actions. End-to-end verified pipelines.
Runner Scale Set Client
Standalone Go module for custom runner autoscaling without Kubernetes. Build autoscaling on VMs, serverless, or any infrastructure.
jobs:
build-arm:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- run: uname -m # aarch64
- run: npm ci && npm test
# Artifact attestation
jobs:
build:
permissions:
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: actions/attest-build-provenance@v2
with:
subject-path: dist/