Skip to content

GitHub Actions Reference: Workflows, Matrix Builds, Docker GHCR, OIDC & Reusable Workflows

GitHub Actions is GitHub’s built-in CI/CD platform. Workflows are YAML files in .github/workflows/ that trigger on push, PR, schedule, or manual dispatch — running jobs in parallel or series on GitHub-hosted or self-hosted runners.

1. Workflow Syntax Essentials

on triggers, jobs, steps, env, and context variables
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
    paths:                          # only run if these paths change
      - 'src/**'
      - 'package.json'
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 9 * * 1-5'          # 9am weekdays UTC
  workflow_dispatch:                # manual trigger
    inputs:
      environment:
        type: choice
        options: [staging, production]
        default: staging

jobs:
  test:
    runs-on: ubuntu-latest          # or: macos-latest, windows-latest, ubuntu-22.04
    timeout-minutes: 30             # fail job if it takes > 30min

    env:
      NODE_ENV: test
      API_URL: https://api.staging.example.com

    steps:
      - name: Checkout code
        uses: actions/checkout@v4   # most common first step

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: npm                # built-in npm/yarn/pnpm cache

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test
        env:
          DB_URL: ${{ secrets.TEST_DB_URL }}   # from repo/org secrets

      - name: Upload test results
        if: failure()               # only runs if previous step failed
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: test-results/

2. Matrix Builds

Test across multiple versions and OS in parallel
jobs:
  test:
    strategy:
      fail-fast: false             # don't cancel other matrix jobs on failure
      matrix:
        python-version: ["3.10", "3.11", "3.12"]
        os: [ubuntu-latest, macos-latest]
        # Total: 3 × 2 = 6 parallel jobs

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

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pip install -r requirements.txt && pytest

      # Exclude specific combinations:
      # matrix.exclude:
      #   - os: macos-latest
      #     python-version: "3.10"

      # Add extra entries to the matrix:
      # matrix.include:
      #   - os: ubuntu-latest
      #     python-version: "3.13-beta"  # test pre-release

  build:
    needs: test                    # runs after ALL matrix test jobs pass
    runs-on: ubuntu-latest
    steps:
      - run: echo "All tests passed, building..."

3. Docker Build & Push to GHCR

Build and push container images to GitHub Container Registry
jobs:
  build-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write              # needed to push to GHCR

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}  # auto-provided, no setup needed

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch         # branch name
            type=semver,pattern={{version}}  # git tag → version
            type=sha,prefix=sha-          # commit SHA

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}  # push only on merge
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha               # use GitHub Actions cache for layers
          cache-to: type=gha,mode=max

4. Reusable Workflows & Caching

DRY workflow composition and dependency caching
# Reusable workflow (define once, call from multiple workflows):
# .github/workflows/deploy.yml
on:
  workflow_call:                   # makes this reusable
    inputs:
      environment:
        required: true
        type: string
    secrets:
      DEPLOY_KEY:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}   # environment protection rules apply
    steps:
      - run: echo "Deploying to ${{ inputs.environment }}"

# Call from another workflow:
# .github/workflows/cd.yml
jobs:
  deploy-staging:
    uses: ./.github/workflows/deploy.yml    # local reusable workflow
    with:
      environment: staging
    secrets:
      DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}

  deploy-production:
    needs: deploy-staging
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
    secrets:
      DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}

# Cache dependencies (manual — for frameworks actions/setup-* don't cover):
- name: Cache pip packages
  uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

# Cache Go modules:
- uses: actions/cache@v4
  with:
    path: |
      ~/go/pkg/mod
      ~/.cache/go-build
    key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}

5. Secrets, OIDC & Environments

Secure secrets, AWS/GCP OIDC auth (no long-lived keys), and deployment environments
# Secrets: Settings → Secrets → Actions
# Access in workflow: ${{ secrets.MY_SECRET }}
# Organization secrets: available across repos
# Environment secrets: only available when running in that environment

# OIDC — authenticate to AWS without storing access keys:
jobs:
  deploy:
    permissions:
      id-token: write              # required for OIDC
      contents: read

    steps:
      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions-role
          aws-region: us-east-1
          # No secrets needed — GitHub mints a token, AWS validates it
          # IAM trust policy must allow github.com OIDC provider + repo claim

      - run: aws s3 ls my-bucket    # authenticated via OIDC

# Deployment environments (require approval before deploying to production):
jobs:
  deploy:
    environment: production        # waits for required reviewers
    steps:
      - run: echo "Deploying to production"
# Set up in: Settings → Environments → Required reviewers

# Concurrency (cancel in-progress runs when new commit pushed):
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true        # cancels previous run if new push arrives

# Conditional steps:
- name: Deploy only on main
  if: github.ref == 'refs/heads/main'
  run: ./deploy.sh

- name: Notify on failure
  if: failure() && github.ref == 'refs/heads/main'   # only alert on main failures
  run: curl -X POST $SLACK_WEBHOOK -d '{"text":"Build failed!"}'

Track GitHub Actions, Node.js, Python, and CI/CD tool releases.
ReleaseRun monitors Kubernetes, Docker, and 13+ DevOps technologies.

Related: GitLab CI Reference | Tekton Reference | Trivy Reference

📱

Watch as a Web Story
5 GitHub Actions Mistakes That Slow Down Every Build — quick visual guide, 2 min

🔍 Audit your actions: Paste your workflow YAML into the GitHub Actions Version Auditor — check every uses: pin against the latest release, see SHA vs tag pinning, and flag unverified publishers.

🔒 Security audit: Use the GitHub Actions Security Checker to scan your workflows for pull_request_target misuse, missing permissions blocks, and hardcoded secrets.

Founded

2023 in London, UK

Contact

hello@releaserun.com