GitHub Actions

Purpose

GitHub Actions is GitHub’s native CI/CD and automation platform. Workflows are YAML files in .github/workflows/ that run on events (push, PR, schedule, manual dispatch). Each workflow is composed of jobs, which run on a runner and execute a sequence of steps. Actions from the marketplace can be composed like functions. See CD Pipelines for GitHub Actions used as a full ML CI/CD pipeline.

Architecture

Event (push / pull_request / schedule / workflow_dispatch)
    │
    ▼
Workflow (.github/workflows/*.yml)
    ├── Job A  (runs-on: ubuntu-latest)
    │   ├── Step 1: actions/checkout@v4
    │   ├── Step 2: actions/setup-python@v5
    │   ├── Step 3: pip install ...
    │   └── Step 4: pytest
    └── Job B  (needs: [A])        ← sequential dependency
        └── Step 1: deploy

Jobs run in parallel by default; needs: creates sequential dependencies. Each job runs in a fresh VM or container.

Implementation Notes

Anatomy of a Workflow

name: CI
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12"]
 
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
 
      - name: Install dependencies
        run: pip install -e ".[test]"
 
      - name: Run tests
        run: pytest --cov=src --cov-report=xml
 
      - uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

Caching Dependencies

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

Secrets and Environment Variables

- name: Deploy
  env:
    API_KEY: ${{ secrets.MY_API_KEY }}     # encrypted secret from repo settings
    DEBUG: "false"                         # plain env var
  run: ./deploy.sh

Secrets are set in Repo Settings → Secrets and variables → Actions. They are never exposed in logs (GitHub replaces them with ***).

Conditional Steps and Jobs

- name: Deploy to production
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
  run: ./deploy.sh
 
# Skip job if no relevant files changed
jobs:
  test:
    if: github.event.pull_request.draft == false

Reusable Workflows

# .github/workflows/reusable-test.yml
on:
  workflow_call:
    inputs:
      python-version:
        required: true
        type: string
 
# Caller
jobs:
  call:
    uses: ./.github/workflows/reusable-test.yml
    with:
      python-version: "3.12"

Docker Build and Push

- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}
 
- uses: docker/build-push-action@v5
  with:
    context: .
    push: ${{ github.ref == 'refs/heads/main' }}
    tags: ghcr.io/${{ github.repository }}:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max

Self-Hosted Runners

Register a runner:

# On the runner machine
./config.sh --url https://github.com/ORG/REPO --token TOKEN
./run.sh    # or install as a service

Use in workflow:

runs-on: self-hosted  # or label like: [self-hosted, gpu]

Self-hosted runners are required for: GPU jobs, large RAM, private network access, or custom software.

OIDC for Cloud Authentication (No Long-Lived Secrets)

permissions:
  id-token: write
  contents: read
 
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789:role/github-actions
    aws-region: eu-west-1

Configure the IAM role to trust GitHub’s OIDC provider for the specific repo and branch.

Trade-offs

ChoiceProCon
GitHub-hosted runnersZero infra, auto-updatedNo GPU, 7 GB RAM, slow for large deps
Self-hosted runnersGPU, custom env, fastInfra overhead, security responsibility
actions/cacheSpeeds up repeated installsCache invalidation on key change
Matrix buildsWide compatibility coverageLong wall-clock time, high minutes usage
OIDC cloud authNo long-lived secretsPer-cloud setup required
Reusable workflowsDRY, versionedHarder to debug cross-repo

References