Skip to content

GitHub Actions — Patterns

OWNER: alex, tjitte
ALSO_USED_BY: leon, marta, iwona
LAST_VERIFIED: 2026-03-26
GE_STACK_VERSION: GitHub Actions (github-hosted runners, ubuntu-latest)


Overview

Workflow patterns used in GE: reusable workflows, composite actions,
matrix builds, caching strategies, secrets management, and environment
protection rules. All patterns optimised for the GE monorepo and
multi-project structure.


Reusable Workflows

Reusable workflows extract shared CI/CD logic into callable units.
GE prefixes these with reusable- for clarity.

Defining a Reusable Workflow

# .github/workflows/reusable-docker-build.yml  
name: Reusable Docker Build  

on:  
  workflow_call:  
    inputs:  
      image-name:  
        description: "Docker image name"  
        required: true  
        type: string  
      context:  
        description: "Build context path"  
        required: true  
        type: string  
    secrets:  
      registry-token:  
        required: true  

jobs:  
  build:  
    runs-on: ubuntu-latest  
    timeout-minutes: 15  
    steps:  
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2  
      - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2  # v3.10.0  
      - uses: docker/build-push-action@263435318d21b8e681c14492fe198e19c816612b  # v6.18.0  
        with:  
          context: ${{ inputs.context }}  
          tags: ${{ inputs.image-name }}:${{ github.sha }}  
          cache-from: type=gha  
          cache-to: type=gha,mode=max  
          push: true  

Calling a Reusable Workflow

jobs:  
  build-executor:  
    uses: ./.github/workflows/reusable-docker-build.yml  
    with:  
      image-name: ge-executor  
      context: ./ge_agent  
    secrets:  
      registry-token: ${{ secrets.REGISTRY_TOKEN }}  

CHECK: reusable workflows have clear input/output contracts
CHECK: all inputs have description and type
CHECK: secrets are passed explicitly, not inherited


Composite Actions

Composite actions bundle multiple steps into a single action.
Use for steps shared within a single workflow (not across workflows).

# .github/actions/setup-ge/action.yml  
name: "Setup GE Environment"  
description: "Install dependencies and configure GE development environment"  

inputs:  
  node-version:  
    description: "Node.js version"  
    default: "22"  

runs:  
  using: composite  
  steps:  
    - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020  # v4.4.0  
      with:  
        node-version: ${{ inputs.node-version }}  
    - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda  # v4.1.0  
      with:  
        version: 9  
    - run: pnpm install --frozen-lockfile  
      shell: bash  
    - run: pnpm exec playwright install --with-deps chromium  
      shell: bash  
      if: ${{ hashFiles('playwright.config.ts') != '' }}  

CHECK: composite actions live in .github/actions/{name}/action.yml
CHECK: all run steps specify shell: bash


Matrix Builds

Matrix builds run jobs across multiple configurations simultaneously.
GE uses matrices for multi-project testing in the monorepo.

jobs:  
  test:  
    runs-on: ubuntu-latest  
    timeout-minutes: 20  
    strategy:  
      fail-fast: false  
      matrix:  
        project:  
          - admin-ui  
          - ge-orchestrator  
          - ge-executor  
        include:  
          - project: admin-ui  
            test-command: "pnpm test"  
            working-directory: "./admin-ui"  
          - project: ge-orchestrator  
            test-command: "pytest"  
            working-directory: "./ge-orchestrator"  
          - project: ge-executor  
            test-command: "pytest"  
            working-directory: "./ge_agent"  
    steps:  
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2  
      - run: ${{ matrix.test-command }}  
        working-directory: ${{ matrix.working-directory }}  

CHECK: fail-fast: false — do not cancel other matrix jobs when one fails
CHECK: matrix size stays under 256 combinations (GitHub limit)


Caching

Node Modules (pnpm)

- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684  # v4.2.3  
  with:  
    path: ~/.pnpm-store  
    key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}  
    restore-keys: |  
      pnpm-${{ runner.os }}-  

Docker Layer Caching

- uses: docker/build-push-action@263435318d21b8e681c14492fe198e19c816612b  # v6.18.0  
  with:  
    cache-from: type=gha  
    cache-to: type=gha,mode=max  

Python (pip)

- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684  # v4.2.3  
  with:  
    path: ~/.cache/pip  
    key: pip-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}  
    restore-keys: |  
      pip-${{ runner.os }}-  

CHECK: cache keys include lockfile hash for cache invalidation
CHECK: restore-keys provides fallback for partial matches
CHECK: cache size monitored — clean up if approaching repository limit


Secrets Management

Environment Secrets

GE uses GitHub Environments with secrets scoped per deployment zone:

Environment Purpose Secrets
development Zone 1 deploys UpCloud dev credentials
staging Zone 2 deploys UpCloud staging credentials
production Zone 3 deploys UpCloud production credentials, bunny.net API key
jobs:  
  deploy:  
    runs-on: ubuntu-latest  
    environment: staging  
    steps:  
      - run: echo "Deploying with ${{ secrets.UPCLOUD_TOKEN }}"  

CHECK: secrets are scoped to the narrowest environment
CHECK: production secrets only accessible from production environment
READ_ALSO: wiki/docs/stack/github-actions/security.md

Repository Secrets

For secrets needed across all environments (e.g., code coverage tokens):

steps:  
  - run: echo "Token is ${{ secrets.CODECOV_TOKEN }}"  

CHECK: prefer environment secrets over repository secrets
CHECK: never log secrets — use ::add-mask:: if dynamic masking is needed


Environment Protection Rules

GE enforces protection rules on staging and production environments:

Rule Staging Production
Required reviewers 1 (thijmen) 2 (rutger + arjan)
Wait timer None 5 minutes
Branch restriction main only main only
Deployment branches main, release/* main, release/*
jobs:  
  deploy-production:  
    runs-on: ubuntu-latest  
    environment:  
      name: production  
      url: https://app.example.com  
    steps:  
      # Deployment steps...  

CHECK: production deploys require 2 reviewers
CHECK: deployments only from main or release/* branches
CHECK: wait timer gives time to cancel accidental deploys


Workflow Dispatch

For manual triggers (emergency deploys, maintenance tasks):

on:  
  workflow_dispatch:  
    inputs:  
      zone:  
        description: "Deployment zone"  
        required: true  
        type: choice  
        options:  
          - staging  
          - production  
      reason:  
        description: "Reason for manual deploy"  
        required: true  
        type: string  

CHECK: manual workflows require a reason input for audit trail
CHECK: workflow_dispatch inputs have descriptions and types


Job Dependencies

jobs:  
  lint:  
    runs-on: ubuntu-latest  
    # ...  
  test:  
    runs-on: ubuntu-latest  
    # ...  
  build:  
    runs-on: ubuntu-latest  
    needs: [lint, test]  
    # ...  
  deploy:  
    runs-on: ubuntu-latest  
    needs: [build]  
    if: github.ref == 'refs/heads/main'  
    # ...  

CHECK: deploy jobs depend on build
CHECK: deploy jobs have if condition for branch restriction
CHECK: independent jobs (lint, test) run in parallel


Artifact Passing

jobs:  
  build:  
    steps:  
      - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02  # v4.6.2  
        with:  
          name: docker-image  
          path: image.tar  
          retention-days: 1  

  deploy:  
    needs: build  
    steps:  
      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093  # v4.3.0  
        with:  
          name: docker-image  

CHECK: artifact retention set to minimum needed (1 day for CI, 7 for releases)
CHECK: artifact names are descriptive


Cross-References

READ_ALSO: wiki/docs/stack/github-actions/index.md
READ_ALSO: wiki/docs/stack/github-actions/security.md
READ_ALSO: wiki/docs/stack/github-actions/pitfalls.md
READ_ALSO: wiki/docs/stack/github-actions/checklist.md