GitHub Actions
GitHub Actions workflow structure, triggers, matrix builds, secrets, and the Domus deployment pattern.
Workflow Structure
Minimal workflow — trigger, job, steps
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Hello from CI"
Every workflow lives in .github/workflows/*.yml. The filename becomes the workflow ID.
Manual trigger with inputs — workflow_dispatch
on:
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
Triggers
Common trigger patterns
on:
push:
branches: [main, 'release/**']
paths:
- 'src/**'
- '!src/**/*.md' # exclude markdown changes
pull_request:
types: [opened, synchronize, reopened]
schedule:
- cron: '0 6 * * 1' # every Monday at 06:00 UTC
repository_dispatch:
types: [component-updated]
Path filters skip CI for docs-only changes. Save runner minutes.
Steps and Actions
Checkout, setup, build — the standard pattern
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install and build
run: |
npm ci
npm run build
- name: Test
run: npm test
npm ci is faster than npm install in CI — it installs from lockfile exactly, no resolution step.
Conditional step — only run on main branch
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: ./deploy.sh
Secrets and Environment
Using secrets — never hardcode credentials
- name: Deploy to Cloudflare
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: domus-docs
directory: ./build/site
Secrets are masked in logs automatically. Set them in Settings > Secrets and variables > Actions.
Environment variables — job-level and step-level
jobs:
build:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- name: Build
env:
API_URL: https://api.example.com
run: npm run build
Matrix Builds
Test across multiple versions
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: |
pip install -r requirements.txt
pytest
Concurrency and Caching
Prevent parallel deploys — cancel in-progress runs
concurrency:
group: "deploy-${{ github.ref }}"
cancel-in-progress: true
Cache dependencies — avoid reinstalling every run
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
restore-keys: |
pip-${{ runner.os }}-
Domus Deployment Pattern
Antora hub-and-spoke — Cloudflare Pages auto-deploys from spoke push
# Spoke repos (domus-captures, etc.):
# Push to main → Cloudflare Pages auto-builds
# No workflow needed in spoke repos
# Hub repo (domus-docs) backup workflow:
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
repository_dispatch:
types: [component-updated]
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npx antora antora-playbook.yml
- uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: domus-docs
directory: ./build/site
gh CLI for Actions
Manage workflows from the terminal
# List recent runs
gh run list --limit 5
# View a specific run with logs
gh run view 12345 --log
# Trigger a workflow manually
gh workflow run deploy.yml -f environment=staging
# Watch a running workflow in real time
gh run watch