Audience: Developers, DevOps engineers, platform engineers, SREs, security engineers, technical leads, and architects
Level: Beginner → Intermediate → Advanced → Enterprise
Last verified: 2026-09-26
Primary source: GitHub Actions official documentation, plus the supplied 62-section topic map
Goal: Teach GitHub Actions as a working CI/CD and automation platform—not merely as YAML syntax.
Main Curriculum — Table of Contents
Hands-on labs
- Lab A — Production-quality Node.js CI
- Lab B — Reusable organization CI
- Lab C — Secure AWS OIDC deployment
- Lab D — Container-to-Kubernetes deployment
- Lab E — Terraform plan / approval / apply
- Lab F — ARC runner fleet for private infrastructure
- Lab G — Systematic workflow debugging
Suggested learning paths
| Goal | Recommended chapters |
|---|---|
| New to GitHub Actions | 1–12, then Lab A |
| Application CI/CD engineer | 1–22, 25–31, 52–56 |
| DevOps / cloud engineer | 1–22, 28–40, Labs C–F |
| Platform engineer | 19, 28–49, 55–62 |
| Security engineer | 10–11, 14, 18, 28–30, 45–48, 60–61 |
| Enterprise administrator | 37–40, 45–51, 56–62 |
How to use this handbook
This handbook is deliberately progressive:
- Foundations — learn the execution model, YAML, triggers, jobs, steps, runners, variables, contexts, expressions, secrets, and tokens.
- Essentials — build production CI/CD pipelines with matrices, caches, artifacts, environments, reusable workflows, containers, and concurrency.
- Advanced engineering — custom actions, security, OIDC, attestations, Docker, Kubernetes, cloud deployments, IaC, ARC, monorepos, cross-repository workflows, APIs, and governance.
- Production operations — performance, cost, observability, cancellation, testing, maintenance, limits, migration, anti-patterns, and enterprise administration.
- End-to-end labs — reusable examples that can be adapted to real repositories.
Conventions used
| Marker | Meaning |
|---|---|
| Mental model | The simplest way to think about a concept |
| Use case | A situation where the feature is useful |
| Production note | An operational recommendation |
| Security note | A security-sensitive behavior |
| Common mistake | A frequent source of workflow failures |
| Lab | A hands-on exercise |
| 2026 note | Version-sensitive behavior verified against current docs |
A note on action versions
Examples use contemporary major versions such as:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
- uses: actions/cache@v4
- uses: actions/upload-artifact@v4
- uses: actions/download-artifact@v5
For production, security-sensitive third-party actions should be pinned to a reviewed full commit SHA. Major tags are easier to read in teaching examples but are mutable references.
Part I — Foundations
1. BASIC — GitHub Actions Foundations
1.1 What is GitHub Actions?
GitHub Actions is GitHub’s built-in automation platform. It reacts to events in or around a repository and runs one or more automated tasks.
The most familiar use is CI/CD:
- compile code;
- run tests;
- lint and format;
- build packages or container images;
- deploy applications;
- publish releases.
But GitHub Actions is broader than CI/CD. It can also automate repository administration, issue management, security scanning, scheduled maintenance, release notes, infrastructure changes, or API-driven orchestration.
Mental model
Think of GitHub Actions as:
Event -> Workflow -> Jobs -> Steps -> Commands/Actions -> Result
A repository event occurs. GitHub finds matching workflow files. Each workflow creates a workflow run. Jobs are scheduled to runners. Each runner executes steps.
flowchart LR
A[GitHub or external event] --> B[Workflow trigger]
B --> C[Workflow run]
C --> D1[Job: lint]
C --> D2[Job: test]
D1 --> E1[Runner]
D2 --> E2[Runner]
E1 --> F1[Steps]
E2 --> F2[Steps]
F1 --> G[Conclusion]
F2 --> G
1.2 CI, Continuous Delivery, and Continuous Deployment
These terms are related but not identical.
| Practice | Main question | Typical GitHub Actions behavior |
|---|---|---|
| Continuous Integration | “Is this change safe to merge?” | Build, lint, unit test, integration test |
| Continuous Delivery | “Is a release ready to deploy?” | Build, package, publish, stage, wait for approval |
| Continuous Deployment | “Can every validated change automatically reach production?” | CI + automatic production deployment |
| Repository automation | “Can repetitive GitHub work be automated?” | Labels, issues, PR comments, releases, housekeeping |
| DevOps automation | “Can code and operations be connected?” | IaC, cloud deployment, security scans, notifications |
Example: smallest useful CI workflow
Create:
.github/workflows/ci.yml
name: CI
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Run a command
run: echo "CI is running"
That file contains the core of GitHub Actions:
on— when to run;jobs— what units of work exist;runs-on— where a job runs;steps— ordered tasks inside a job;uses— invoke an action;run— execute a shell command.
1.3 Core terminology
| Term | Meaning |
|---|---|
| Workflow | YAML-defined automation stored in .github/workflows |
| Workflow run | One execution instance of a workflow |
| Event | Activity that can trigger a workflow |
| Trigger | Workflow configuration matching an event |
| Job | Independently scheduled unit of work |
| Step | Ordered task inside a job |
| Action | Reusable executable automation component |
| Runner | Machine executing a job |
| Command | Shell statement executed by a run step |
| Artifact | File retained from a workflow |
| Cache | Reusable dependency/build data intended to speed later runs |
| Context | Structured runtime information, such as github or matrix |
| Expression | ${{ ... }} logic evaluated by GitHub |
| Secret | Protected sensitive value |
| Variable | Non-sensitive configuration value |
| Environment | Named deployment target with variables, secrets, and protections |
1.4 Architecture
GitHub Actions has two broad planes:
- Control plane — GitHub receives events, evaluates workflows, creates runs, schedules jobs, manages permissions, logs, artifacts, caches, and API objects.
- Execution plane — runners execute jobs.
flowchart TB
subgraph GitHub["GitHub control plane"]
R[Repository]
E[Event]
W[Workflow parser]
S[Scheduler]
T[Token / permissions]
L[Logs, cache, artifacts]
R --> E --> W --> S
T --> S
S --> L
end
subgraph Exec["Execution plane"]
H1[GitHub-hosted runner]
H2[Larger runner]
H3[Self-hosted runner]
H4[ARC ephemeral runner pod]
end
S --> H1
S --> H2
S --> H3
S --> H4
H1 --> L
H2 --> L
H3 --> L
H4 --> L
1.5 Workflow lifecycle
A simplified lifecycle is:
flowchart TD
A[Event occurs] --> B{Workflow trigger matches?}
B -- No --> Z[Nothing runs]
B -- Yes --> C[Create workflow run]
C --> D[Evaluate workflow-level expressions]
D --> E[Create eligible jobs]
E --> F{Job dependencies satisfied?}
F -- No --> G[Wait / skip]
F -- Yes --> H[Queue job]
H --> I[Assign compatible runner]
I --> J[Prepare job]
J --> K[Execute steps]
K --> L[Post-job cleanup]
L --> M[Upload logs/results]
M --> N[Calculate job conclusion]
N --> O[Calculate workflow conclusion]
A workflow can be:
- queued;
- in progress;
- waiting for an environment approval;
- completed;
- cancelled.
Jobs and steps have their own statuses and conclusions.
1.6 Workflow files
Workflow files must be placed under:
.github/workflows/
Both extensions are valid:
ci.yml
release.yaml
A repository can contain many workflows:
.github/
└── workflows/
├── ci.yml
├── security.yml
├── release.yml
├── deploy-staging.yml
└── deploy-production.yml
Naming recommendation
Use a filename for humans and a name for the UI:
name: Pull Request CI
Good filenames:
pull-request-ci.yml
release.yml
deploy-production.yml
terraform-plan.yml
Avoid:
workflow1.yml
new.yml
test2.yml
1.7 Basic workflow structure
name: Application CI
run-name: >-
CI for ${{ github.ref_name }} by @${{ github.actor }}
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
env:
NODE_ENV: test
defaults:
run:
shell: bash
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: "24"
- name: Install
run: npm ci
- name: Test
run: npm test
Key hierarchy
Workflow
├── metadata/configuration
│ ├── name
│ ├── run-name
│ ├── on
│ ├── permissions
│ ├── env
│ ├── defaults
│ └── concurrency
└── jobs
└── job_id
├── runs-on
├── permissions
├── needs
├── if
├── environment
├── strategy
└── steps
├── uses
└── run
2. FUNDAMENTALS — Workflow Syntax and Execution
2.1 YAML fundamentals for GitHub Actions
YAML is whitespace-sensitive. Indentation represents structure.
Mappings
name: CI
name is a key and CI is its value.
Nested mappings
permissions:
contents: read
Sequences
branches:
- main
- release/**
Short form:
branches: [main, release/**]
Multiline strings
Literal style preserves line breaks:
run: |
echo "first"
echo "second"
Folded style joins lines where possible:
run-name: >-
Deploy ${{ github.ref_name }}
by ${{ github.actor }}
Strings and quoting
When ambiguity is possible, quote values:
node-version: "24"
Quoting is especially useful for:
- version numbers;
- strings containing
:,#,{,}; - values that YAML might interpret as another type.
Comments
# Run only on main
on:
push:
branches: [main]
Common YAML mistake: indentation
Wrong:
jobs:
build:
runs-on: ubuntu-latest
Correct:
jobs:
build:
runs-on: ubuntu-latest
2.2 YAML anchors and aliases
Modern GitHub Actions supports YAML anchors for reuse.
env: &common-env
NODE_ENV: test
CI: "true"
jobs:
unit:
runs-on: ubuntu-latest
env: *common-env
steps:
- run: npm test
integration:
runs-on: ubuntu-latest
env: *common-env
steps:
- run: npm run test:integration
Use anchors for small static YAML reuse. Use reusable workflows or composite actions for meaningful automation reuse.
| Reuse tool | Best for |
|---|---|
| YAML anchor | Repeating small mappings/lists |
| Composite action | Repeating a sequence of steps |
| Reusable workflow | Repeating one or more complete jobs |
| Workflow template | Standard starting point for many repositories |
2.3 Workflow-level syntax
Important top-level keys:
| Key | Purpose |
|---|---|
name | Workflow display name |
run-name | Dynamic run display name |
on | Triggers |
permissions | Default GITHUB_TOKEN permissions |
env | Workflow-wide environment variables |
defaults | Default shell or working directory |
concurrency | Serialize/cancel related runs |
cache-mode | Control workflow cache read/write access |
jobs | Job definitions |
defaults.run
defaults:
run:
shell: bash
working-directory: ./app
This applies to run steps unless overridden.
2.4 Job-level syntax
A job is identified by its map key:
jobs:
unit_tests:
name: Unit Tests
runs-on: ubuntu-latest
Useful job-level keys:
jobs:
deploy:
name: Deploy
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
environment:
name: production
url: https://example.com
timeout-minutes: 30
concurrency:
group: production
queue: max
env:
APP_NAME: demo
steps:
- run: ./deploy.sh
2.5 Step-level syntax
A step either invokes an action or runs a command.
Action step:
- name: Checkout
uses: actions/checkout@v6
Shell step:
- name: Test
run: npm test
Configured action:
- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm
Conditional step:
- name: Publish
if: github.ref == 'refs/heads/main'
run: npm publish
Step-specific environment:
- name: Integration tests
env:
API_URL: https://staging.example.com
run: npm run test:integration
2.6 Execution boundaries matter
Each job normally gets its own runner. Therefore:
- files are not automatically shared between jobs;
- environment variables are not automatically shared between jobs;
- processes are not shared between jobs;
- use outputs, artifacts, external stores, or reusable workflow outputs to pass data.
Inside a job, steps share the same filesystem, but each run step starts its own process.
This does not persist:
- run: export VERSION=1.2.3
- run: echo "$VERSION" # empty
This does persist to later steps:
- run: echo "VERSION=1.2.3" >> "$GITHUB_ENV"
- run: echo "$VERSION"
2.7 2026 syntax: background and parallel steps
GitHub Actions now supports asynchronous/background steps and parallel step groups.
Background step
steps:
- name: Start server
id: server
run: npm start
background: true
- name: Run tests
run: npm test
- name: Stop server
cancel: server
Parallel group
steps:
- uses: actions/checkout@v6
- parallel:
- name: Build frontend
run: npm run build:frontend
- name: Build backend
run: npm run build:backend
- name: Build docs
run: npm run build:docs
- name: Test combined result
run: npm test
Use job-level parallelism for strong isolation and independent runners. Use step-level parallelism only when the tasks belong in the same job and can safely share the same runner.
3. FUNDAMENTALS — Events and Workflow Triggers
3.1 Trigger model
on tells GitHub when a workflow is eligible to run.
on: push
Multiple triggers:
on:
push:
pull_request:
workflow_dispatch:
Filtered trigger:
on:
push:
branches:
- main
- "release/**"
paths:
- "src/**"
- "!src/docs/**"
3.2 Event categories
| Category | Examples | Use cases |
|---|---|---|
| Code activity | push, pull_request | CI |
| Review activity | pull_request_review, issue_comment | approval/automation |
| Release activity | release, registry_package | publishing |
| Repository lifecycle | create, delete, fork, public | governance |
| Deployment | deployment, deployment_status | CD integration |
| Manual | workflow_dispatch | operator-triggered jobs |
| Schedule | schedule | nightly scans, maintenance |
| External | repository_dispatch | API-driven automation |
| Reuse | workflow_call | reusable workflows |
| Chaining | workflow_run | privileged follow-up workflows |
| Merge queue | merge_group | required checks for merge queues |
3.3 Important events
push
on:
push:
branches: [main]
tags:
- "v*"
Use for:
- branch CI;
- main-branch builds;
- tag-based releases.
pull_request
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [main]
Use for untrusted PR validation. Fork PRs receive restricted tokens and normally do not receive repository secrets.
pull_request_target
This event runs in the security context of the base repository.
Use it for metadata operations such as:
- adding labels;
- commenting;
- assigning reviewers;
- triage.
Do not use it to execute untrusted PR code with privileged credentials.
workflow_dispatch
on:
workflow_dispatch:
inputs:
environment:
description: Deployment environment
type: environment
required: true
version:
description: Version to deploy
type: string
required: true
dry_run:
description: Dry run
type: boolean
default: true
Reference inputs:
${{ inputs.environment }}
${{ inputs.version }}
${{ inputs.dry_run }}
schedule
on:
schedule:
- cron: "15 2 * * 1-5"
Scheduled workflows run from the default branch. Treat cron as UTC unless the current GitHub feature/documentation for your account explicitly supports otherwise.
Common cron examples:
| Schedule | Cron |
|---|---|
| Every hour | 0 * * * * |
| Daily 02:00 | 0 2 * * * |
| Weekdays 09:00 | 0 9 * * 1-5 |
| Sundays midnight | 0 0 * * 0 |
repository_dispatch
Use when another system should trigger a repository workflow.
on:
repository_dispatch:
types: [deploy-request]
Payload:
{
"event_type": "deploy-request",
"client_payload": {
"environment": "staging",
"version": "1.4.7"
}
}
Use:
${{ github.event.client_payload.environment }}
workflow_run
Useful for privilege separation:
on:
workflow_run:
workflows: ["PR CI"]
types: [completed]
A common architecture is:
flowchart LR
A[Untrusted PR] --> B[Low-privilege CI workflow]
B --> C[Tests + artifact]
C --> D[workflow_run]
D --> E[Privileged follow-up workflow]
E --> F[Comment / publish / deploy]
The downstream workflow must still treat artifacts and data produced by the untrusted upstream workflow as untrusted input.
3.4 Filters
Branches
on:
push:
branches:
- main
- "release/**"
Branch ignore
on:
push:
branches-ignore:
- "docs/**"
Tags
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
Paths
on:
pull_request:
paths:
- "backend/**"
- ".github/workflows/backend-ci.yml"
Negative patterns
paths:
- "src/**"
- "!src/docs/**"
Order matters when positive and negative patterns are combined.
3.5 Event payloads
Useful references:
- name: Show event
run: |
echo "name=$GITHUB_EVENT_NAME"
cat "$GITHUB_EVENT_PATH"
Expression equivalent:
${{ github.event }}
Do not dump entire contexts into production logs without considering sensitive values.
4. FUNDAMENTALS — Jobs
4.1 Job architecture
Jobs run in parallel by default.
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: echo lint
test:
runs-on: ubuntu-latest
steps:
- run: echo test
flowchart LR
W[Workflow] --> L[Lint job]
W --> T[Test job]
W --> S[Security job]
4.2 Sequential jobs with needs
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo build
test:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- run: echo deploy
flowchart LR
B[Build] --> T[Test] --> D[Deploy]
4.3 Fan-out and fan-in
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo build
unit:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo unit
integration:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo integration
security:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo security
deploy:
needs: [unit, integration, security]
runs-on: ubuntu-latest
steps:
- run: echo deploy
flowchart LR
B[Build] --> U[Unit]
B --> I[Integration]
B --> S[Security]
U --> D[Deploy]
I --> D
S --> D
4.4 Job conditions
if: github.ref == 'refs/heads/main'
Status functions:
if: success()
if: failure()
if: cancelled()
if: always()
Cleanup pattern:
cleanup:
if: always()
needs: [test]
runs-on: ubuntu-latest
steps:
- run: ./cleanup.sh
Prefer:
if: ${{ !cancelled() }}
when you want downstream processing after success/failure but do not want expensive work after explicit cancellation.
4.5 Job outputs
Step → job → dependent job:
jobs:
version:
runs-on: ubuntu-latest
outputs:
release_version: ${{ steps.v.outputs.value }}
steps:
- id: v
run: echo "value=1.8.0" >> "$GITHUB_OUTPUT"
deploy:
needs: version
runs-on: ubuntu-latest
steps:
- run: echo "Deploying ${{ needs.version.outputs.release_version }}"
Flow:
flowchart LR
S[Step output] --> J[Job output]
J --> N[needs context]
N --> D[Dependent job]
4.6 Job conclusions
Typical conclusions include:
- success;
- failure;
- cancelled;
- skipped.
A downstream job normally skips if a required dependency fails unless its if expression says otherwise.
5. FUNDAMENTALS — Steps
5.1 Step types
Action step
- uses: actions/checkout@v6
Command step
- run: npm test
5.2 Step IDs and outputs
- name: Calculate version
id: version
run: echo "value=2.1.0" >> "$GITHUB_OUTPUT"
- name: Use version
run: echo "${{ steps.version.outputs.value }}"
5.3 Step conditions
- name: Production-only step
if: github.ref == 'refs/heads/main'
run: ./publish.sh
5.4 Step environment
- name: Test
env:
API_URL: https://api.example.com
run: npm test
5.5 Failure and continue-on-error
- name: Optional scan
continue-on-error: true
run: ./non-blocking-scan.sh
Use this carefully. It changes failure semantics and can hide defects.
A better pattern is to make the intent visible:
- name: Experimental compatibility test
id: experimental
continue-on-error: true
run: npm run test:experimental
- name: Report experimental result
if: always()
run: echo "Experimental result = ${{ steps.experimental.outcome }}"
5.6 Timeouts
- name: External integration test
timeout-minutes: 10
run: npm run test:integration
Also set job timeouts:
jobs:
test:
timeout-minutes: 20
Production workflows should generally avoid the default six-hour job timeout unless the workload truly needs it.
5.7 Shells
Common shells:
shell: bash
shell: sh
shell: pwsh
shell: cmd
shell: python
Custom shell:
shell: python {0}
Example:
- name: Python inline script
shell: python
run: |
import platform
print(platform.platform())
5.8 Exit codes
For most shells:
- exit
0= success; - non-zero = failure.
Bash strict mode is often useful:
- name: Robust script
run: |
set -euo pipefail
./build.sh
But understand what it means:
| Option | Meaning |
|---|---|
-e | stop on failing command |
-u | error on undefined variables |
-o pipefail | pipeline fails if any component fails |
6. ESSENTIALS — Runners
6.1 What is a runner?
A runner is the machine or compute environment that executes a job.
GitHub Actions supports:
- GitHub-hosted runners;
- larger GitHub-hosted runners;
- self-hosted runners;
- Kubernetes-based ephemeral runners using Actions Runner Controller.
flowchart TD
J[Queued job] --> M{Matching runner?}
M -->|GitHub managed| H[Hosted runner]
M -->|Large managed| L[Larger runner]
M -->|Customer managed| S[Self-hosted runner]
M -->|Kubernetes| A[ARC runner pod]
H --> X[Execute job]
L --> X
S --> X
A --> X
6.2 runs-on
Basic:
runs-on: ubuntu-latest
Specific image:
runs-on: ubuntu-26.04
Multiple self-hosted labels:
runs-on: [self-hosted, linux, x64, prod]
Expression:
runs-on: ${{ matrix.os }}
6.3 GitHub-hosted runners
Advantages:
- fresh VM for each job;
- no runner maintenance;
- preinstalled tooling;
- strong isolation between jobs;
- simple scaling.
Trade-offs:
- ephemeral filesystem;
- public-network orientation unless private networking is configured;
- image contents change over time;
- fixed resource classes for standard runners.
2026 note
Current GitHub-hosted runner labels include modern images such as ubuntu-26.04 and newer macOS/Windows images. *-latest means GitHub’s latest stable supported runner image, not necessarily the newest operating-system version published by the OS vendor.
6.4 Larger runners
Use larger runners when you need:
- more CPU/RAM/disk;
- GPU;
- autoscaling managed by GitHub;
- runner groups;
- static IP ranges;
- supported private networking options;
- custom images where available.
Decision example:
| Requirement | Standard hosted | Larger runner | Self-hosted / ARC |
|---|---|---|---|
| Zero maintenance | Excellent | Excellent | Low |
| Static egress IP | Poor | Strong | Strong |
| Private network | Limited/options | Stronger options | Full control |
| Custom base image | Limited | Supported in eligible configs | Full |
| GPU | Specialized/limited | Yes | Yes |
| Strong internal isolation control | Medium | Medium/High | Highest control |
| Cost predictability | Simple | Per-minute | Infra + operations |
6.5 Self-hosted runners
Self-hosted runners are customer-operated machines.
They can live on:
- bare metal;
- VM;
- cloud instance;
- on-premises server;
- container/ephemeral VM;
- Kubernetes via ARC.
Architecture
flowchart LR
G[GitHub Actions service] <-->|Outbound HTTPS| R[Self-hosted runner]
R --> C[Repository checkout]
R --> P[Private package registry]
R --> D[Database]
R --> K[Kubernetes/API]
R --> A[Cloud APIs]
Normally, the runner initiates outbound connections to GitHub. You do not generally expose an inbound runner control port to the internet.
6.6 Persistent vs ephemeral
Persistent runner
The same host runs multiple jobs over time.
Risks:
- leftover files;
- credentials not cleaned correctly;
- modified toolchains;
- poisoned caches;
- malicious persistence.
Ephemeral runner
Created for one job and destroyed afterward.
Benefits:
- clean environment;
- less state leakage;
- easier trust boundaries;
- safer autoscaling architecture.
For sensitive production or untrusted workloads, ephemeral runners are usually preferable.
6.7 Runner groups
Runner groups control which repositories can use groups of runners.
Example organizational model:
Runner Groups
├── public-ci
├── internal-ci
├── production-deploy
└── privileged-infra
Use groups to enforce trust boundaries. A production deployment runner should not normally be available to every repository.
6.8 Private networking
Typical methods include:
- self-hosted runner in a VPC/VNet;
- larger runner private networking where supported;
- hosted runner private-network connectivity patterns such as supported overlay/API-gateway approaches;
- proxy;
- VPN/overlay;
- private endpoints.
Use case: private EKS deployment
flowchart LR
G[GitHub] --> R[Ephemeral self-hosted runner<br/>private subnet]
R --> E[EKS private API]
R --> ECR[ECR]
R --> S[Secrets manager]
The runner has network reachability. GitHub itself does not need direct access to the private cluster.
6.9 Runner security checklist
- Prefer ephemeral runners.
- Segment runner networks by trust level.
- Do not allow untrusted public-repository PRs onto privileged self-hosted runners.
- Avoid storing long-lived secrets on runner disks.
- Patch runner OS and runner application.
- Restrict metadata-service/cloud-instance credentials.
- Restrict outbound network access where practical.
- Use short-lived cloud credentials via OIDC.
- Monitor runner registration and removal.
- Remove stale runners.
- Use runner groups and repository allowlists.
7. ESSENTIALS — Variables and Environment Variables
7.1 Three major variable mechanisms
| Mechanism | Syntax | Sensitive? | Typical use |
|---|---|---|---|
| Environment variable | env: / $NAME | No | process configuration |
| Configuration variable | ${{ vars.NAME }} | No | reusable repo/org/env configuration |
| Secret | ${{ secrets.NAME }} | Yes | tokens, passwords, keys |
7.2 env scopes
Workflow:
env:
APP_NAME: catalog
Job:
jobs:
build:
env:
MODE: ci
Step:
- run: echo "$MODE"
env:
MODE: debug
The more specific scope overrides the broader scope.
7.3 Configuration variables
Examples:
env:
API_URL: ${{ vars.API_URL }}
Variables can be configured at organization, repository, or environment level.
A lower-level configuration variable generally takes precedence over a higher-level one, but remember that environment-level variables become available after the job starts and do not behave exactly like pre-run env resolution in every location.
7.4 Default variables
Useful defaults include:
CI
GITHUB_ACTION
GITHUB_ACTION_PATH
GITHUB_ACTION_REPOSITORY
GITHUB_ACTIONS
GITHUB_ACTOR
GITHUB_ACTOR_ID
GITHUB_API_URL
GITHUB_BASE_REF
GITHUB_ENV
GITHUB_EVENT_NAME
GITHUB_EVENT_PATH
GITHUB_GRAPHQL_URL
GITHUB_HEAD_REF
GITHUB_JOB
GITHUB_OUTPUT
GITHUB_PATH
GITHUB_REF
GITHUB_REF_NAME
GITHUB_REF_PROTECTED
GITHUB_REF_TYPE
GITHUB_REPOSITORY
GITHUB_REPOSITORY_ID
GITHUB_REPOSITORY_OWNER
GITHUB_RETENTION_DAYS
GITHUB_RUN_ATTEMPT
GITHUB_RUN_ID
GITHUB_RUN_NUMBER
GITHUB_SERVER_URL
GITHUB_SHA
GITHUB_STEP_SUMMARY
GITHUB_TRIGGERING_ACTOR
GITHUB_WORKFLOW
GITHUB_WORKFLOW_REF
GITHUB_WORKFLOW_SHA
GITHUB_WORKSPACE
RUNNER_ARCH
RUNNER_ENVIRONMENT
RUNNER_NAME
RUNNER_OS
RUNNER_TEMP
RUNNER_TOOL_CACHE
Example
- name: Metadata
run: |
echo "repository=$GITHUB_REPOSITORY"
echo "sha=$GITHUB_SHA"
echo "ref=$GITHUB_REF"
echo "runner=$RUNNER_OS/$RUNNER_ARCH"
7.5 Runtime-created environment variables
- name: Calculate
run: echo "RELEASE_VERSION=2.4.0" >> "$GITHUB_ENV"
- name: Use
run: echo "$RELEASE_VERSION"
The creating step does not receive the newly written value; later steps do.
8. ESSENTIALS — Contexts
8.1 Contexts vs environment variables
Contexts are evaluated by GitHub during workflow processing.
Environment variables are typically expanded by the shell on the runner.
Example:
- run: echo "${{ github.repository }}"
GitHub expression.
- run: echo "$GITHUB_REPOSITORY"
Shell expansion.
8.2 Major contexts
| Context | Contains |
|---|---|
github | event, repository, ref, SHA, actor, workflow metadata |
env | workflow/job/step env values |
vars | configuration variables |
job | current job status/container/service metadata |
steps | step outcomes and outputs |
runner | runner OS, arch, temp, tool cache |
secrets | secrets available to the job |
strategy | matrix strategy metadata |
matrix | current matrix combination |
needs | dependency job results and outputs |
inputs | manual/reusable workflow inputs |
jobs | called-workflow job outputs in supported reusable-workflow contexts |
8.3 Property access
Dot notation:
${{ github.repository }}
Index notation:
${{ github['repository'] }}
Index notation is useful when keys are dynamic or contain characters unsuitable for dot notation.
8.4 Context debugging
Safer targeted debugging:
- name: Debug selected GitHub values
run: |
echo "event=${{ github.event_name }}"
echo "ref=${{ github.ref }}"
echo "sha=${{ github.sha }}"
Avoid casually dumping:
${{ toJSON(github) }}
because the github context can contain sensitive information such as tokens.
8.5 Context availability
Not every context is available in every workflow key.
For example:
matrixexists only for matrix jobs;needsexists when jobs declare dependencies;stepsexists during step execution;- secrets are unavailable in some untrusted/fork contexts.
When GitHub reports:
Unrecognized named-value
check whether the context is legal at that point in workflow evaluation.
9. ESSENTIALS — Expressions
9.1 Syntax
${{ expression }}
Example:
if: ${{ github.ref == 'refs/heads/main' }}
For many if: clauses, the outer ${{ }} is optional:
if: github.ref == 'refs/heads/main'
9.2 Operators
Common operators:
!
>
>=
<
<=
==
!=
&&
||
Example:
if: github.ref == 'refs/heads/main' && !cancelled()
9.3 Useful functions
contains
if: contains(github.event.pull_request.labels.*.name, 'safe-to-deploy')
startsWith
if: startsWith(github.ref, 'refs/tags/v')
endsWith
if: endsWith(github.event.pull_request.title, '[release]')
format
env:
IMAGE: ${{ format('{0}:{1}', vars.REGISTRY, github.sha) }}
join
${{ join(matrix.targets, ',') }}
toJSON
env:
MATRIX_JSON: ${{ toJSON(matrix) }}
fromJSON
Turn JSON text into structured data:
strategy:
matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }}
hashFiles
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
9.4 Status functions
if: success()
if: failure()
if: cancelled()
if: always()
Cleanup pattern
- name: Collect diagnostics
if: failure()
run: ./collect-diagnostics.sh
- name: Cleanup
if: always()
run: ./cleanup.sh
9.5 Expression security
Dangerous:
- run: echo "${{ github.event.pull_request.title }}"
A malicious title can become part of generated shell code.
Safer:
- name: Validate title
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
printf '%s\n' "$PR_TITLE"
The key rule is:
Treat user-controlled event fields as untrusted data, not executable syntax.
10. ESSENTIALS — Secrets
10.1 Secret scopes
Secrets can exist at:
- organization;
- repository;
- environment.
Use environment secrets for deployment-specific sensitive values.
10.2 Referencing a secret
- name: Login
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
run: ./login.sh
Prefer passing secrets through environment variables or action inputs rather than interpolating them directly into shell syntax.
10.3 Secret masking
GitHub attempts to mask known secrets from logs.
Do not assume masking is a data-loss-prevention system.
A secret can still leak if:
- transformed/encoded;
- embedded in another value;
- written to an artifact;
- sent to an external server;
- exposed by malicious code;
- stored on a persistent self-hosted runner.
10.4 Fork and Dependabot behavior
Untrusted pull requests and Dependabot-triggered workflows are intentionally restricted.
Do not design CI that requires powerful write credentials merely to test arbitrary PR code.
Architect separate trust zones:
flowchart LR
A[Untrusted code] --> B[Read-only CI]
B --> C[Test result / sanitized artifact]
C --> D[Trusted workflow]
D --> E[Privileged operation]
10.5 Secret rotation
A practical rotation pattern:
- issue new credential;
- add/update GitHub secret;
- validate a workflow;
- revoke old credential;
- review audit logs;
- document rotation date/owner.
Better still, eliminate many long-lived cloud secrets with OIDC.
10.6 Structured and large secrets
If an application needs structured secret material, prefer a secret manager or carefully encoded file material.
Example:
- name: Restore certificate
env:
CERT_B64: ${{ secrets.CERT_B64 }}
run: |
printf '%s' "$CERT_B64" | base64 --decode > certificate.p12
Delete sensitive files during cleanup, especially on self-hosted runners.
11. ESSENTIALS — GITHUB_TOKEN
11.1 What is GITHUB_TOKEN?
For each job, GitHub automatically creates a short-lived token that can authenticate to GitHub APIs and repository resources within the permissions granted to that job.
You do not create or rotate this token manually.
- name: Show authenticated user
env:
GH_TOKEN: ${{ github.token }}
run: gh api user
In many cases, actions access it implicitly through:
${{ secrets.GITHUB_TOKEN }}
or:
${{ github.token }}
11.2 Permission model
Start with minimum privileges:
permissions:
contents: read
Add only what is needed.
Example: create a GitHub Release:
permissions:
contents: write
Example: cloud OIDC:
permissions:
contents: read
id-token: write
Example: artifact attestation:
permissions:
contents: read
id-token: write
attestations: write
Common permissions
| Permission | Typical reason |
|---|---|
contents | clone/read/write repository contents and releases |
actions | work with workflow runs/actions |
checks | create/update checks |
deployments | deployment objects |
discussions | discussions |
id-token | request OIDC token |
issues | issues |
packages | GitHub Packages / GHCR |
pages | GitHub Pages |
pull-requests | PR metadata/comments |
security-events | upload security results |
statuses | commit statuses |
attestations | artifact attestations |
artifact-metadata | linked/artifact metadata capabilities where used |
11.3 Workflow vs job permissions
Workflow default:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
release:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- run: gh release create ...
The release job gets extra permissions without granting them to the test job.
11.4 Read-all / write-all
GitHub supports broad forms such as:
permissions: read-all
Avoid:
permissions: write-all
unless there is an unusually strong reason.
11.5 Token lifetime
The token is tied to a job and is short-lived. Do not treat it as a long-term credential or persist it outside the workflow.
11.6 Token-triggered workflow behavior
GitHub deliberately prevents many actions performed with the repository’s GITHUB_TOKEN from recursively creating uncontrolled workflow chains.
If you need deliberate cross-repository or broader automation, consider:
- GitHub App token;
- fine-grained PAT;
repository_dispatch;- reusable workflows;
workflow_run.
Choose the narrowest identity and permissions that solve the problem.
12. ESSENTIALS — Workflow Commands
Workflow commands let a running step communicate structured information back to GitHub Actions.
12.1 Environment files
Modern workflows use environment files instead of deprecated output commands.
| File variable | Purpose |
|---|---|
GITHUB_ENV | values for later steps |
GITHUB_OUTPUT | step outputs |
GITHUB_PATH | add directories to PATH |
GITHUB_STEP_SUMMARY | Markdown job summary |
GITHUB_STATE | state between action phases |
12.2 GITHUB_ENV
- name: Set runtime value
run: echo "BUILD_CHANNEL=stable" >> "$GITHUB_ENV"
- name: Use runtime value
run: echo "$BUILD_CHANNEL"
12.3 GITHUB_OUTPUT
- name: Calculate digest
id: digest
run: echo "sha=$(sha256sum app.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
- name: Print digest
run: echo "${{ steps.digest.outputs.sha }}"
12.4 GITHUB_PATH
- name: Install internal tools
run: |
mkdir -p "$HOME/bin"
cp ./tools/mycli "$HOME/bin/"
echo "$HOME/bin" >> "$GITHUB_PATH"
Later steps can execute:
- run: mycli --version
12.5 GITHUB_STEP_SUMMARY
- name: Publish test summary
run: |
{
echo "## Test Summary"
echo ""
echo "| Suite | Result |"
echo "|---|---|"
echo "| Unit | ✅ Passed |"
echo "| Integration | ✅ Passed |"
} >> "$GITHUB_STEP_SUMMARY"
This creates a human-readable summary in the Actions UI.
12.6 Annotations
- run: echo "::notice title=Build::Compilation started"
- run: echo "::warning file=app.js,line=8::Deprecated API"
- run: echo "::error file=app.js,line=42::Validation failed"
Use annotations to make actionable errors visible near code.
12.7 Log grouping
- name: Grouped diagnostics
run: |
echo "::group::Environment"
env | sort
echo "::endgroup::"
Be careful not to print secrets.
12.8 Masking
- name: Mask dynamic value
run: |
TOKEN="$(./generate-temporary-token.sh)"
echo "::add-mask::$TOKEN"
Mask before printing or passing a dynamically generated sensitive value.
13. ESSENTIALS — Matrix Builds
13.1 Why matrices exist
A matrix expands one job definition into multiple jobs.
Use cases:
- multiple OSs;
- multiple runtime versions;
- multiple architectures;
- multiple components;
- test permutations.
13.2 Basic matrix
jobs:
test:
strategy:
matrix:
node: [22, 24]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test
Produces two jobs.
13.3 Multi-dimensional matrix
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [22, 24]
runs-on: ${{ matrix.os }}
Expansion:
| Job | OS | Node |
|---|---|---|
| 1 | Ubuntu | 22 |
| 2 | Ubuntu | 24 |
| 3 | Windows | 22 |
| 4 | Windows | 24 |
13.4 Include
Add special combinations:
strategy:
matrix:
node: [22, 24]
include:
- node: 25
experimental: true
13.5 Exclude
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [20, 22, 24]
exclude:
- os: windows-latest
node: 20
13.6 fail-fast
strategy:
fail-fast: false
Use false when you want all matrix results even after one fails.
Use true when later combinations are not worth running after a mandatory combination fails.
13.7 Experimental combinations
jobs:
test:
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: true
matrix:
node: [22, 24]
experimental: [false]
include:
- node: 25
experimental: true
Stable failures block the workflow. Experimental failures do not.
13.8 max-parallel
strategy:
max-parallel: 4
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8]
Useful when:
- test infrastructure can handle only limited concurrency;
- licenses are scarce;
- a database would overload;
- cost needs control.
13.9 Dynamic matrix
Producer:
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.m.outputs.matrix }}
steps:
- id: m
run: |
echo 'matrix={"service":["api","worker","frontend"]}' >> "$GITHUB_OUTPUT"
Consumer:
build:
needs: prepare
strategy:
matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }}
runs-on: ubuntu-latest
steps:
- run: echo "Building ${{ matrix.service }}"
This is a powerful monorepo pattern.
14. ESSENTIALS — Caching
14.1 Cache vs artifact
A cache is a performance optimization.
An artifact is a retained workflow output.
| Characteristic | Cache | Artifact |
|---|---|---|
| Main purpose | speed | transfer/retain files |
| Correctness dependency | should be optional | may be part of pipeline |
| Key lookup | yes | name/ID |
| Typical content | package manager cache | binaries, reports, plans |
| Mutable | effectively replaced with new keys | upload produces artifact |
| Security concern | poisoning | sensitive data / provenance |
Golden rule:
Your build should remain correct if every cache disappears.
14.2 Package-manager caching
Simple Node example:
- uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm
- run: npm ci
For many ecosystems, setup actions provide automatic dependency caching.
14.3 actions/cache
- name: Cache npm data
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
14.4 Cache-key design
A good key encodes the inputs that make cache contents valid:
ecosystem + OS + architecture + toolchain + dependency lock hash
Example:
key: >-
npm-${{ runner.os }}-${{ runner.arch }}-node24-
${{ hashFiles('**/package-lock.json') }}
14.5 Cache poisoning
If untrusted code can write to a cache later consumed by a privileged workflow, an attacker may plant malicious content.
Mitigations:
- do not let low-trust workflows write privileged caches;
- use restore-only cache in untrusted workflows;
- use trust-separated cache keys;
- do not cache executable security-sensitive state unnecessarily.
14.6 cache-mode
Current GitHub Actions supports:
| Mode | Restore | Save |
|---|---|---|
read | Yes | No |
write | Yes | Yes |
write-only | No | Yes |
none | No | No |
Example:
cache-mode: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('**/package-lock.json') }}
Use read for low-trust PR validation unless there is a deliberate reason to save.
15. ESSENTIALS — Artifacts
15.1 What is an artifact?
Artifacts are files produced by workflows and stored by GitHub for later use.
Examples:
- compiled binary;
- package;
- Terraform plan;
- test report;
- coverage;
- logs;
- SBOM;
- deployment bundle.
15.2 Upload
- name: Upload package
uses: actions/upload-artifact@v4
with:
name: app-package
path: dist/
retention-days: 14
15.3 Download
- name: Download package
uses: actions/download-artifact@v5
with:
name: app-package
path: dist/
15.4 Sharing between jobs
flowchart LR
B[Build job] -->|upload| A[(Artifact)]
A -->|download| T[Test job]
A -->|download| D[Deploy job]
Example:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: web-dist
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v5
with:
name: web-dist
path: dist/
- run: ./deploy.sh dist/
15.5 Build once, deploy many
A mature pipeline builds one immutable artifact and promotes that exact artifact.
flowchart LR
C[Commit] --> B[Build once]
B --> A[(Immutable artifact)]
A --> S[Staging]
S --> U[UAT]
U --> P[Production]
Do not rebuild from source separately for each environment unless that behavior is intentionally part of the release model.
15.6 Retention
Set retention based on purpose:
| Artifact | Example retention |
|---|---|
| temporary test logs | 3–7 days |
| PR build | 7–14 days |
| release candidate | 30–90 days |
| formal release | preferably durable package/release registry |
| audit/provenance evidence | per compliance requirements |
16. ESSENTIALS — Concurrency
Concurrency prevents conflicting or wasteful simultaneous runs.
16.1 Cancel stale CI
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
If a developer pushes three commits quickly, old CI can be cancelled.
16.2 Serialize production deployments
concurrency:
group: production
queue: max
This keeps one production deployment active while allowing pending deployment runs to queue.
2026 note
Current syntax supports queue: max to retain multiple pending runs in the same concurrency group. It cannot be combined with cancel-in-progress: true.
16.3 Environment-specific groups
concurrency:
group: deploy-${{ inputs.environment }}
queue: max
Staging and production can progress independently, while each environment remains serialized.
16.4 Avoid accidental cross-workflow cancellation
Bad:
concurrency:
group: main
Two different workflows can share the same repository-level concurrency group.
Better:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
17. ESSENTIALS — Containers
17.1 Container jobs
A job can run inside a container:
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:24-bookworm
steps:
- uses: actions/checkout@v6
- run: node --version
- run: npm ci
- run: npm test
This makes the job environment more deterministic.
17.2 Container credentials
container:
image: ghcr.io/my-org/ci-image:2026.09
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
17.3 Environment, volumes, ports, options
container:
image: ubuntu:24.04
env:
LANG: C.UTF-8
volumes:
- ci-cache:/cache
options: --cpus 2
Use only runner/container options supported by GitHub’s runner/container implementation.
17.4 Service containers
Service containers are companion services available during a job.
PostgreSQL:
jobs:
integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17
env:
POSTGRES_USER: app
POSTGRES_PASSWORD: test-password
POSTGRES_DB: app_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U app -d app_test"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v6
- name: Integration tests
env:
DATABASE_URL: postgresql://app:test-password@localhost:5432/app_test
run: npm run test:integration
Other common services:
Redis
MySQL
MongoDB
RabbitMQ
Elasticsearch/OpenSearch
17.5 Container networking mental model
If the job itself runs directly on the runner and services expose ports:
job process -> localhost:mapped-port -> service
If the job runs in a job container, service containers are typically addressable through their service labels on the shared container network.
18. ESSENTIALS — Using Actions
18.1 What is an action?
An action is a reusable unit that runs inside a step.
- uses: actions/checkout@v6
Sources include:
- GitHub official actions;
- Marketplace/community actions;
- private actions;
- actions in the same repository.
18.2 Action references
Major tag:
uses: actions/checkout@v6
Specific tag:
uses: some-org/some-action@v2.3.1
Commit SHA:
uses: some-org/some-action@0123456789abcdef0123456789abcdef01234567
For third-party production actions, a reviewed full SHA is the strongest common pinning strategy.
18.3 Local actions
Modern same-repository reference:
- uses: $/.github/actions/setup-project
Traditional relative form:
- uses: actions/checkout@v6
- uses: ./.github/actions/setup-project
The $-rooted repository action reference avoids depending on the checked-out workspace path in supported GitHub.com workflows.
18.4 Third-party action review checklist
Before approving an action:
- Who maintains it?
- Is the source public and reviewable?
- Is it widely used?
- Is release provenance credible?
- What permissions does it need?
- Does it execute downloaded code?
- Does it send data externally?
- Is it pinned?
- Are dependencies maintained?
- Does it handle secrets?
- Is there an equivalent first-party action?
18.5 Dependabot for Actions
A typical configuration:
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
This helps keep action references current. Security review is still required.
19. ESSENTIALS — Reusable Automation
19.1 Reusable Workflows
Reusable workflows centralize full jobs.
Called workflow:
.github/workflows/reusable-ci.yml
name: Reusable CI
on:
workflow_call:
inputs:
node-version:
type: string
required: false
default: "24"
secrets:
npm-token:
required: false
outputs:
package-name:
description: Package artifact name
value: ${{ jobs.build.outputs.package-name }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
package-name: ${{ steps.meta.outputs.name }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: ${{ inputs.node-version }}
cache: npm
- run: npm ci
- run: npm test
- run: npm run build
- id: meta
run: echo "name=web-${{ github.sha }}" >> "$GITHUB_OUTPUT"
Caller:
jobs:
ci:
uses: my-org/platform-workflows/.github/workflows/reusable-ci.yml@v3
with:
node-version: "24"
secrets: inherit
19.2 Reusable workflow design
flowchart TB
R1[Repo A] --> W[Central reusable workflow]
R2[Repo B] --> W
R3[Repo C] --> W
W --> S[Standard CI behavior]
Good candidates:
- language CI standards;
- security scanning;
- container build/publish;
- Terraform plan/apply;
- deployment logic;
- compliance steps.
19.3 Inputs and secrets
Prefer explicit inputs for behavior:
inputs:
environment:
type: string
required: true
Use explicit secret contracts when possible.
secrets: inherit is convenient, but it gives the called workflow access to all caller secrets available under the inheritance rules. Use it deliberately.
19.4 Outputs
Reusable workflows can expose workflow outputs so the caller can consume computed data.
Use them for:
- artifact name;
- image digest;
- release version;
- deployment URL;
- plan status.
19.5 Nesting
Reusable workflows can call reusable workflows.
Do not create deeply tangled call graphs merely because nesting is allowed. Keep ownership and privilege boundaries obvious.
2026 note
Current GitHub documentation allows up to 10 levels of connected reusable workflows and up to 50 unique reusable workflows reachable from a top-level workflow file. Treat limits as version-sensitive and check current documentation when designing very large platforms.
19.6 Composite actions vs reusable workflows
| Need | Composite action | Reusable workflow |
|---|---|---|
| Reuse steps | Excellent | Indirectly |
| Reuse entire jobs | No | Yes |
| Choose runner | No | Yes |
| Job permissions | No | Yes |
| Environment gates | No | Yes |
| Matrix jobs | No | Yes |
| Simple setup routine | Excellent | Overkill |
| Full CI standard | Limited | Excellent |
19.7 Workflow templates
Organization workflow templates help repositories bootstrap approved workflow structures.
Use templates when:
- repositories need a standardized starting point;
- teams may customize afterward;
- a central reusable workflow is not enough by itself.
20. ESSENTIALS — CI Pipelines
20.1 A production CI pipeline
A mature CI pipeline commonly performs:
flowchart LR
C[Checkout] --> I[Install]
I --> L[Lint]
I --> U[Unit tests]
I --> S[Security]
L --> B[Build]
U --> B
S --> B
B --> A[Artifact]
20.2 Node.js example
name: Pull Request CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
cache-mode: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm
- run: npm ci
- name: Lint
run: npm run lint
- name: Unit tests
run: npm test -- --ci
- name: Build
run: npm run build
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: web-${{ github.sha }}
path: dist/
retention-days: 7
20.3 Integration-test job
integration:
needs: quality
runs-on: ubuntu-latest
timeout-minutes: 20
services:
postgres:
image: postgres:17
env:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U app -d test"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm
- run: npm ci
- run: npm run test:integration
env:
DATABASE_URL: postgresql://app:app@localhost:5432/test
20.4 Required checks
Configure branch protection/rulesets so critical CI jobs must succeed before merge.
Examples:
lint
unit
integration
security
build
The exact enforcement belongs in repository rules/rulesets—not merely in workflow YAML.
21. ESSENTIALS — CD Pipelines
21.1 Delivery vs deployment
A good CD design separates:
- build — create deployable artifact;
- verify — prove artifact quality;
- promote — choose where it goes;
- deploy — apply change;
- verify deployment — prove runtime health;
- rollback — recover if needed.
flowchart LR
C[Commit] --> B[Build]
B --> T[Test]
T --> A[(Artifact)]
A --> D[Development]
D --> S[Staging]
S --> G{Approval / protection}
G --> P[Production]
P --> V[Smoke / health tests]
V -->|Fail| R[Rollback]
21.2 Basic staged deployment
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: ./build.sh
- uses: actions/upload-artifact@v4
with:
name: release
path: dist/
staging:
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/download-artifact@v5
with:
name: release
- run: ./deploy.sh staging
production:
needs: staging
runs-on: ubuntu-latest
environment: production
concurrency:
group: production
queue: max
steps:
- uses: actions/download-artifact@v5
with:
name: release
- run: ./deploy.sh production
Environment settings, not the YAML alone, should enforce production approval/protection.
21.3 Rollback patterns
Rollback can mean:
- redeploy previous artifact;
- revert Git commit;
- restore Kubernetes ReplicaSet;
- switch blue/green target;
- reduce canary traffic to zero;
- revert Terraform/IaC change with a reviewed plan.
The rollback design must match the deployment technology. “Run deploy again” is not a rollback strategy by itself.
22. ESSENTIALS — Environments and Deployments
22.1 What is a GitHub environment?
An environment models a deployment target such as:
development
staging
uat
production
It can hold:
- environment variables;
- environment secrets;
- allowed branches/tags;
- required reviewers;
- wait timers;
- custom deployment protection rules;
- deployment history.
22.2 Reference an environment
Simple:
environment: production
With URL:
environment:
name: production
url: https://app.example.com
Dynamic:
environment:
name: ${{ inputs.environment }}
22.3 Environment secrets
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- env:
API_KEY: ${{ secrets.API_KEY }}
run: ./deploy.sh
Environment secrets do not become available to the job until the applicable environment protections are satisfied.
22.4 Required reviewers
A production environment can require human approval.
flowchart LR
J[Deployment job] --> W[Waiting]
W --> A{Approved?}
A -- No --> X[Rejected / timeout]
A -- Yes --> R[Runner starts]
R --> S[Environment secrets available]
S --> D[Deploy]
22.5 Wait timers
A wait timer can delay deployment after triggering.
Use cases:
- controlled release window;
- soak period;
- progressive delivery delay.
22.6 Branch/tag restrictions
Allow only appropriate refs to deploy:
production:
protected branches only
or selected patterns according to repository policy.
22.7 Custom deployment protection rules
GitHub Apps can gate a deployment using external systems such as:
- observability;
- change-management approval;
- security systems;
- quality gates.
A sophisticated flow:
flowchart LR
A[Deploy requested] --> G[GitHub environment gate]
G --> O[Observability / ITSM / security app]
O -->|Approve| D[Deployment]
O -->|Reject| F[Stop]
22.8 Environment without deployment object
Current workflow syntax can use an environment’s secrets/variables and protections without creating a deployment record:
environment:
name: staging
deployment: false
This can be useful for CI or tests that need protected environment configuration but are not actual deployments.
Custom GitHub-App deployment protection rules require a deployment object, so they are not compatible with deployment: false.
22.9 Environment concurrency
Environments do not automatically solve every deployment race. Add concurrency:
concurrency:
group: deploy-${{ inputs.environment }}
queue: max
22.10 Recommended environment model
| Environment | Approval | Secrets | Concurrency | Typical source |
|---|---|---|---|---|
| development | no | low privilege | optional | feature/main |
| staging | optional | staging-only | serialize | main |
| UAT | often | UAT-only | serialize | promoted artifact |
| production | yes/protection rule | production-only | serialize | approved release |
Part II — Advanced Engineering
23. ADVANCED — Custom Actions
Custom actions package repeatable behavior behind a stable interface.
Use a custom action when repeated workflow steps represent a reusable capability rather than a full pipeline.
23.1 Types of custom actions
GitHub supports three major custom-action models:
| Type | Runs on | Strengths | Trade-offs |
|---|---|---|---|
| JavaScript action | Linux/macOS/Windows | Fast startup, cross-platform | Must package dependencies |
| Docker action | Linux | Complete runtime control | Container startup, Linux only |
| Composite action | Runner shell/actions | Easy step reuse | Less control than JS/Docker |
23.2 Action metadata
An action directory contains:
.github/actions/my-action/
├── action.yml
└── ...
Typical metadata:
name: Setup Application
description: Install and prepare the application
inputs:
node-version:
description: Node.js version
required: false
default: "24"
outputs:
cache-key:
description: Effective dependency cache key
value: ${{ steps.cache-key.outputs.value }}
runs:
using: composite
steps:
- uses: actions/setup-node@v7
with:
node-version: ${{ inputs.node-version }}
- id: cache-key
shell: bash
run: |
echo "value=npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}" \
>> "$GITHUB_OUTPUT"
Use:
- uses: $/.github/actions/setup-application
with:
node-version: "24"
23.3 JavaScript action
Project:
hello-action/
├── action.yml
├── package.json
├── src/
│ └── index.js
└── dist/
└── index.js
action.yml:
name: Hello Action
description: Demonstrates a JavaScript action
inputs:
who:
description: Person to greet
required: true
outputs:
message:
description: Generated greeting
runs:
using: node24
main: dist/index.js
src/index.js:
const core = require("@actions/core");
try {
const who = core.getInput("who", { required: true });
const message = `Hello, ${who}!`;
core.info(message);
core.setOutput("message", message);
} catch (error) {
core.setFailed(error.message);
}
Package the dependency tree:
npm install
npx @vercel/ncc build src/index.js -o dist
The committed dist/ bundle makes the action executable without running npm install in the consumer workflow.
Toolkit modules
Common packages include:
@actions/core
@actions/github
@actions/exec
@actions/io
@actions/tool-cache
Use @actions/github for authenticated GitHub API access and Octokit.
23.4 Pre/main/post actions
Actions can have setup and cleanup phases.
Conceptually:
flowchart LR
P[pre] --> M[main] --> O[post]
Use state when the post phase needs information created earlier.
Examples:
- start/stop a service;
- mount/unmount;
- acquire/release temporary resource;
- login/logout.
23.5 Docker action
action.yml:
name: Policy Check
description: Run a policy tool in a controlled container
inputs:
path:
description: Path to scan
required: true
runs:
using: docker
image: Dockerfile
args:
- ${{ inputs.path }}
Dockerfile:
FROM alpine:3.22
RUN apk add --no-cache bash
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh:
#!/usr/bin/env bash
set -euo pipefail
target="$1"
echo "Scanning: $target"
A Docker action should write files that later steps need under the GitHub workspace mount.
23.6 Composite action
Composite actions are ideal for setup sequences.
name: Python Quality
description: Run standardized Python quality checks
inputs:
python-version:
required: false
default: "3.14"
runs:
using: composite
steps:
- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- shell: bash
run: pip install -r requirements-dev.txt
- shell: bash
run: ruff check .
- shell: bash
run: pytest
23.7 Versioning custom actions
For a shared action repository:
v1.0.0 immutable release
v1.1.0 immutable release
v1 moving major tag
Consumers seeking maximum supply-chain assurance can pin the release commit SHA.
24. ADVANCED — Workflow Data Flow
CI/CD design is mostly data movement under trust constraints.
24.1 Data-passing mechanisms
| Mechanism | Scope | Best for |
|---|---|---|
| Shell variable | current process | temporary command logic |
GITHUB_ENV | later steps in same job | runtime configuration |
| Step output | later steps/jobs via mapping | small values |
| Job output | dependent jobs | small values |
| Reusable workflow output | caller | workflow API contract |
| Artifact | jobs/runs | files, bundles, reports |
| Cache | later runs | performance data |
| External store | arbitrary workflows | durable shared state |
24.2 Step → job → workflow
flowchart LR
A[Step] -->|GITHUB_OUTPUT| B[Step output]
B --> C[Job output]
C -->|needs| D[Dependent job]
C --> E[Reusable workflow output]
E --> F[Caller workflow]
24.3 JSON as a workflow interchange format
Producer:
- id: components
run: |
echo 'value=["api","worker","frontend"]' >> "$GITHUB_OUTPUT"
Consumer:
strategy:
matrix:
component: ${{ fromJSON(needs.detect.outputs.components) }}
24.4 Artifact-based transfer
Use artifacts instead of outputs for large data.
Do not put these into outputs:
- binary data;
- large JSON documents;
- Terraform plan files;
- coverage directories;
- container tarballs.
24.5 Trust classification
Before passing data downstream, classify it.
trusted configuration
trusted build output
untrusted PR metadata
untrusted PR artifact
external API response
user-supplied workflow_dispatch input
Do not treat “came from another job” as synonymous with “trusted.”
25. ADVANCED — Conditional Workflows
25.1 Event condition
if: github.event_name == 'push'
25.2 Branch condition
if: github.ref == 'refs/heads/main'
25.3 Tag condition
if: startsWith(github.ref, 'refs/tags/v')
25.4 Actor condition
if: github.actor == 'release-bot'
Use actor checks only as one signal. Do not treat username comparison as a substitute for permissions or environment protection.
25.5 Pull-request condition
if: github.event.pull_request.draft == false
25.6 Matrix condition
if: matrix.os == 'ubuntu-latest'
25.7 Fork detection
if: github.event.pull_request.head.repo.full_name == github.repository
This can separate same-repository PR behavior from fork behavior.
25.8 Failure-aware cleanup
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: ./test.sh
collect:
needs: test
if: failure()
runs-on: ubuntu-latest
steps:
- run: ./collect-remote-logs.sh
25.9 Avoid unreadable conditions
Bad:
if: >
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'workflow_dispatch' && inputs.force == true) ||
...
Better approaches:
- split jobs;
- calculate a decision once;
- use a reusable workflow;
- keep policy in a script with tests.
A workflow should be readable during an incident.
26. ADVANCED — Error Handling
26.1 Failure hierarchy
command exit code
↓
step outcome/conclusion
↓
job conclusion
↓
dependent-job eligibility
↓
workflow conclusion
26.2 Step failure
- run: exit 1
Normally fails the job.
26.3 Controlled non-blocking failure
- id: optional-scan
continue-on-error: true
run: ./scan.sh
Later:
- if: steps.optional-scan.outcome == 'failure'
run: echo "Optional scan failed"
26.4 Matrix failure
Use:
strategy:
fail-fast: false
when every compatibility result matters.
26.5 Retry pattern
GitHub Actions does not mean every operation should be blindly retried.
Good retry candidates:
- transient network request;
- eventually consistent cloud API;
- temporary package mirror error.
Bad retry candidates:
- deterministic unit-test failure;
- compiler error;
- policy violation.
Shell example:
for attempt in 1 2 3; do
if curl --fail --retry 2 https://service.example/health; then
exit 0
fi
sleep $((attempt * 5))
done
exit 1
26.6 Rollback on failure
deploy:
...
verify:
needs: deploy
...
rollback:
needs: [deploy, verify]
if: failure() && needs.deploy.result == 'success'
...
Automated rollback should be used only when rollback itself is well-tested and safe.
26.7 Cancellation-aware cleanup
if: always()
can execute after cancellation in some workflow evaluation paths, depending on where it is used.
For costly downstream jobs, often prefer:
if: ${{ !cancelled() }}
For local resource cleanup that truly must run, use action post steps or carefully designed cleanup steps.
27. ADVANCED — Troubleshooting and Debugging
Troubleshooting GitHub Actions is easier when you identify which layer failed.
flowchart TD
A[Workflow not behaving] --> B{Did workflow start?}
B -- No --> C[Trigger / branch / path / disabled workflow]
B -- Yes --> D{Did job start?}
D -- No --> E[if / needs / environment / runner queue]
D -- Yes --> F{Did step start?}
F -- No --> G[step if / previous failure]
F -- Yes --> H{What failed?}
H --> I[command/action]
H --> J[permission/auth]
H --> K[network/DNS]
H --> L[cache/artifact]
H --> M[service/container]
27.1 Workflow syntax errors
Symptoms:
- workflow not listed;
- invalid workflow file;
- parser error.
Check:
- indentation;
- valid keys;
- expression syntax;
- unsupported context in a key;
- correct
.github/workflowspath.
Use actionlint during development.
27.2 Trigger debugging
Ask:
- Did the event happen?
- Is the workflow file on the relevant branch/default branch?
- Does
onmatch the event? - Do branch filters match?
- Do path filters match?
- Does the event support the expected ref?
- Was the workflow disabled?
- Was the event created by a token behavior that intentionally does not trigger another workflow?
27.3 Context/expression debugging
Print only the properties you need:
- run: |
echo "event=${{ github.event_name }}"
echo "ref=${{ github.ref }}"
echo "head=${{ github.head_ref }}"
echo "base=${{ github.base_ref }}"
For structured debugging:
- env:
MATRIX: ${{ toJSON(matrix) }}
run: printf '%s\n' "$MATRIX"
Avoid whole-context dumps when tokens or secrets may be included.
27.4 Runner debugging
Print:
- run: |
echo "OS=$RUNNER_OS"
echo "ARCH=$RUNNER_ARCH"
echo "ENV=$RUNNER_ENVIRONMENT"
echo "WORKSPACE=$GITHUB_WORKSPACE"
df -h
free -h || true
Self-hosted runner checks:
runner online?
labels correct?
runner group allows repo?
network egress works?
runner version supported?
disk full?
service running?
workspace permissions?
27.5 Authentication failures
Differentiate:
401 -> identity/token invalid or absent
403 -> identity known but permission/policy denies
404 -> resource may be hidden by authorization or truly absent
Check:
- workflow/job
permissions; - environment approval;
- fork restrictions;
- token audience;
- cloud trust policy;
- repository access policy.
27.6 Network troubleshooting
Useful Linux commands:
getent hosts example.com
curl -v https://example.com
nc -vz host.example 443
ip route
cat /etc/resolv.conf
On self-hosted runners also check:
- security groups/firewall;
- proxy;
- private DNS;
- NAT;
- route tables;
- endpoint policies;
- TLS interception.
27.7 Service container failures
Check:
- image starts;
- health check;
- port mapping;
- service hostname;
- credentials;
- startup time;
- runner Docker capability.
27.8 Cache problems
Ask:
- Is the key exactly what you expect?
- Did
hashFilesmatch any files? - Is
cache-modeallowing restore/save? - Is the workflow low-trust?
- Is the cache scoped to a different branch?
- Is the cache stale by design?
27.9 Artifact problems
Check:
- upload path exists;
- hidden files behavior if relevant;
- artifact name;
- download path;
- retention;
- permissions for cross-run/cross-repo access.
27.10 Debug logging
Repository/organization settings can enable runner/step debug logging. Also use targeted verbose flags in your tools.
Do not enable excessive debug logging permanently on secret-heavy production workflows.
27.11 Re-runs
Understand:
GITHUB_RUN_ID stable for the run
GITHUB_RUN_ATTEMPT increases on re-run
A re-run can have a different triggering user, but privileges are based on GitHub’s documented actor behavior. Do not assume “person clicking re-run” automatically changes all permission semantics.
28. ADVANCED — Security Hardening
Security is not a final workflow step. It is part of the workflow’s execution model.
28.1 Trust boundaries
Classify workflow inputs and execution:
flowchart LR
U[Untrusted PR / issue / external input] --> L[Low privilege]
T[Trusted branch / release] --> H[Higher privilege]
L --> V[Validation]
H --> D[Deployment / publish]
28.2 Least privilege
Start:
permissions: {}
or:
permissions:
contents: read
Then grant individual jobs what they need.
Example:
jobs:
test:
permissions:
contents: read
publish:
permissions:
contents: read
packages: write
id-token: write
28.3 Script injection
Dangerous:
- run: |
echo "PR: ${{ github.event.pull_request.title }}"
An attacker controls the title.
Safer:
- env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
printf 'PR: %s\n' "$PR_TITLE"
Best when practical: pass untrusted values as data to an action or program that does not interpret them as shell syntax.
28.4 pull_request vs pull_request_target
| Property | pull_request | pull_request_target |
|---|---|---|
| Primary context | PR/merge code | base repository |
| Fork token | restricted/read-oriented | privileged base context |
| Secrets for fork PR | withheld | potentially available |
| Good for running PR code | Yes, under low privilege | No |
| Good for label/comment triage | Possible with limitations | Yes |
Dangerous pattern
on:
pull_request_target:
jobs:
unsafe:
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install
- run: npm test
This combines privileged context with attacker-controlled code.
Use pull_request_target only when you understand and preserve the trust boundary.
28.5 Third-party action supply chain
A workflow action can:
- read repository files;
- read available secrets;
- use the job token;
- alter outputs;
- make network requests.
Treat action code like any other dependency with execution privileges.
Production controls:
- allowlist actions;
- pin third-party actions by full SHA;
- review source;
- automate update PRs;
- restrict job permissions;
- isolate privileged jobs.
28.6 Self-hosted runner security
Never place public untrusted PR workloads on a persistent runner with access to:
- production networks;
- cloud instance role credentials;
- internal systems;
- signing keys;
- persistent Docker sockets;
- privileged Kubernetes service accounts.
Prefer ephemeral isolated runners.
28.7 Cache poisoning
Low-trust workflow:
cache-mode: read
Privileged trusted workflow:
cache-mode: write
Better: separate cache namespaces where trust levels differ.
28.8 Security baseline
name: Secure CI
on:
pull_request:
permissions:
contents: read
cache-mode: read
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Process untrusted title safely
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: printf '%s\n' "$PR_TITLE"
29. ADVANCED — OpenID Connect
OIDC lets GitHub Actions obtain short-lived cloud credentials without storing a long-lived cloud access key in GitHub.
29.1 Traditional secret model
flowchart LR
C[Cloud access key] --> S[GitHub secret]
S --> W[Workflow]
W --> C2[Cloud API]
Problems:
- manual rotation;
- duplicated long-lived credential;
- blast radius if leaked.
29.2 OIDC model
sequenceDiagram
participant W as GitHub Actions Job
participant G as GitHub OIDC Provider
participant C as Cloud STS/IAM
participant R as Cloud Resource
W->>G: Request OIDC ID token
G-->>W: Signed short-lived ID token
W->>C: Exchange token under trust policy
C-->>W: Short-lived cloud credential
W->>R: Authorized request
29.3 Workflow permission
OIDC requires:
permissions:
contents: read
id-token: write
id-token: write allows the job to request an ID token. It does not itself grant cloud resource access. The cloud-side trust policy decides what the token can assume.
29.4 Claims
Typical claims identify:
- repository;
- owner;
- branch/ref;
- tag;
- environment;
- workflow;
- audience.
Cloud trust policy should be narrow.
29.5 AWS OIDC example
Workflow:
name: AWS Deploy
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@<PINNED_SHA>
with:
role-to-assume: arn:aws:iam::123456789012:role/github-production
aws-region: ap-northeast-1
- run: aws sts get-caller-identity
Illustrative AWS trust condition:
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:*"
}
}
}
Tighten the subject further to the exact branch or environment model you use.
AWS-specific point
AWS does not support GitHub OIDC custom claims in the same way some other providers do. Design AWS trust using supported standard claims and GitHub’s documented subject format.
29.6 Azure OIDC
Typical flow:
permissions:
id-token: write
contents: read
steps:
- uses: azure/login@<PINNED_SHA>
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
Azure federated credentials map GitHub identity claims to an Entra application or managed identity trust.
29.7 Google Cloud Workload Identity Federation
Typical flow:
permissions:
contents: read
id-token: write
steps:
- uses: google-github-actions/auth@<PINNED_SHA>
with:
workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }}
service_account: ${{ vars.GCP_SERVICE_ACCOUNT }}
GCP validates GitHub OIDC assertions through Workload Identity Federation.
29.8 Vault and other providers
Any provider that can validate GitHub’s OIDC issuer and claims can use the model:
GitHub identity -> provider trust policy -> short-lived credential
29.9 2026 immutable subject claims
Current GitHub OIDC documentation includes immutable subject formats for newer/opted-in repositories using numeric owner/repository identifiers in addition to names. This reduces risks from repository renames/transfers.
When establishing new cloud trust in 2026+, inspect the actual sub format emitted for your repository and match the current GitHub OIDC reference rather than copying an old blog’s trust-policy string.
29.10 OIDC security checklist
- grant
id-token: writeonly to jobs that need it; - restrict cloud role subject;
- restrict audience;
- bind production roles to protected environments where appropriate;
- use separate roles per environment;
- keep cloud role permissions least-privilege;
- use short session durations;
- log role assumption;
- never fall back to a long-lived key merely because OIDC trust configuration is inconvenient.
30. ADVANCED — Artifact Attestations
Artifact attestations establish verifiable information about where and how software was built.
30.1 Why provenance matters
A binary can be genuine only if you can answer:
Which repository?
Which workflow?
Which commit?
Which identity?
Which build process?
Was the artifact changed afterward?
30.2 Supply-chain flow
flowchart LR
S[Source commit] --> B[GitHub Actions build]
B --> A[Artifact / image]
B --> P[Provenance attestation]
A --> V[Verifier]
P --> V
V --> D{Policy satisfied?}
D -- Yes --> R[Release / deploy]
D -- No --> X[Reject]
30.3 Permissions
Attestation workflows commonly need:
permissions:
contents: read
id-token: write
attestations: write
Package/container publishing also needs the relevant package permission.
30.4 Container provenance concept
- name: Build and push image
id: push
uses: docker/build-push-action@<PINNED_SHA>
with:
push: true
tags: ghcr.io/my-org/my-app:${{ github.sha }}
- name: Attest image
uses: actions/attest-build-provenance@<PINNED_SHA>
with:
subject-name: ghcr.io/my-org/my-app
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
Use the exact current action/interface documented by GitHub when implementing; attestation features evolve.
30.5 Kubernetes enforcement
A Kubernetes admission policy can reject images without valid trusted provenance.
sequenceDiagram
participant D as Deployment
participant A as Admission Controller
participant R as Registry/Attestation Store
participant K as Kubernetes API
D->>K: Create Pod
K->>A: Admission review
A->>R: Verify image provenance
R-->>A: Attestation evidence
A-->>K: Allow / deny
GitHub documents Sigstore Policy Controller integration for enforcing GitHub artifact attestations.
30.6 What attestations do not solve
Attestations do not prove:
- source code is bug-free;
- dependencies are safe;
- workflow itself is secure;
- maintainer intent is good.
They prove selected provenance/integrity properties. Pair them with code review, dependency security, pinning, branch protection, and isolated builds.
31. ADVANCED — Docker and Container CI/CD
31.1 Container pipeline
flowchart LR
C[Code] --> T[Test]
T --> B[Buildx / BuildKit]
B --> I[Image]
I --> S[Scan]
I --> A[Attest/sign]
S --> P[Push registry]
A --> P
P --> D[Deploy]
31.2 Buildx example
jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@<PINNED_SHA>
- uses: docker/login-action@<PINNED_SHA>
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: build
uses: docker/build-push-action@<PINNED_SHA>
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
31.3 Image tagging
Use immutable tags:
sha-<commit>
release version
image digest
Do not rely on only:
latest
A robust model:
ghcr.io/org/app:sha-a1b2c3...
ghcr.io/org/app:v2.4.1
digest: sha256:...
Deploy by digest for strongest immutability.
31.4 Multi-platform
with:
platforms: linux/amd64,linux/arm64
Multi-platform builds may use emulation or native builders. Measure build time and verify architecture-specific tests.
31.5 Docker layer caching
BuildKit cache can use suitable cache exporters/importers.
The exact cache design depends on:
- builder topology;
- trust model;
- registry;
- build size;
- private dependencies.
Treat build cache as untrusted performance data unless you have a stronger provenance model.
31.6 Image scanning
A release path often includes:
dependency scan
Dockerfile lint
image vulnerability scan
SBOM
policy gate
provenance
Do not fail production simply because “scanner returned any CVE.” Define severity, exploitability, exception, and SLA policies.
32. ADVANCED — Package Publishing
GitHub Actions can publish:
- npm;
- Maven;
- Gradle packages;
- NuGet;
- RubyGems;
- container packages;
- other registries.
32.1 npm publishing model
flowchart LR
T[Tag/release] --> B[Build/test]
B --> A[Package]
A --> P[Registry]
P --> C[Consumers]
Use trusted release triggers and protected publishing credentials.
32.2 GitHub Packages permissions
A package job may need:
permissions:
contents: read
packages: write
32.3 npm example
name: Publish npm
on:
release:
types: [published]
permissions:
contents: read
packages: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: "24"
registry-url: https://npm.pkg.github.com
- run: npm ci
- run: npm test
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
External registries may require registry-specific OIDC/trusted publishing or a secret.
32.4 Package provenance
Where supported, combine package publishing with provenance/attestation so consumers can verify build origin.
33. ADVANCED — Releases
33.1 Tag-driven release
on:
push:
tags:
- "v*"
33.2 Release-event-driven
on:
release:
types: [published]
Choose based on ownership:
- tag is source of truth;
- GitHub Release publication is source of truth;
- an external release manager triggers the workflow.
33.3 Semantic versioning
MAJOR.MINOR.PATCH
Examples:
1.4.3
2.0.0
2.1.0-rc.1
A release workflow should validate version format and prevent accidental republishing.
33.4 Release assets
permissions:
contents: write
steps:
- env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload \
"${{ github.ref_name }}" \
dist/app-linux-amd64 \
dist/app-linux-arm64
33.5 Automated release architecture
flowchart LR
M[Merge to main] --> V[Version decision]
V --> T[Tag]
T --> B[Build once]
B --> A[Artifacts]
A --> R[GitHub Release]
A --> P[Package registry]
R --> D[Deployment promotion]
Keep “build release artifact” separate from “deploy artifact” when possible.
34. ADVANCED — Cloud Deployments
Cloud deployments combine GitHub identity, cloud identity, network reachability, immutable artifacts, and deployment policy.
34.1 Cloud deployment reference architecture
flowchart TB
G[GitHub repository] --> W[Workflow]
W --> O[OIDC]
O --> I[Cloud IAM / STS]
I --> C[Short-lived credentials]
C --> R[Cloud resource]
W --> A[(Build artifact/image)]
A --> R
34.2 AWS
Authentication
Preferred:
GitHub OIDC -> AWS IAM role -> temporary STS credentials
Avoid static AWS access keys in repository secrets when OIDC is available.
S3 deployment
- name: Sync static site
run: aws s3 sync dist/ s3://my-site-bucket/ --delete
Give the role only the required bucket/object permissions.
EC2 deployment
Possible patterns:
- SSM Run Command;
- CodeDeploy;
- immutable AMI + Auto Scaling;
- instance pull from artifact store.
Avoid opening SSH from GitHub-hosted runner ranges merely to copy files to production.
ECR
- name: Login to ECR
id: ecr
uses: aws-actions/amazon-ecr-login@<PINNED_SHA>
Build/push, then deploy by immutable digest.
ECS
Typical flow:
build image -> push ECR -> update task definition -> deploy service -> wait stable
EKS
Typical flow:
OIDC -> IAM role -> EKS auth -> Helm/kubectl -> rollout verification
A private EKS endpoint requires a runner with network reachability into the VPC/network.
Lambda
Patterns:
- zip artifact;
- container image;
- SAM/CDK/CloudFormation;
- direct CLI for simple use cases.
CloudFormation
Production flow:
validate -> create change set -> review/gate -> execute -> monitor
Terraform
Production flow:
fmt -> validate -> security checks -> plan -> review/gate -> apply
Use cloud/OIDC auth and remote-state controls.
34.3 Azure
OIDC architecture:
GitHub OIDC -> Entra federated credential -> Azure identity -> resource
Targets:
- Web Apps;
- Container Apps;
- AKS;
- Functions;
- Azure Storage;
- Terraform.
Keep production identities/environment protections separate from test identities.
34.4 Google Cloud
Preferred model:
GitHub OIDC -> Workload Identity Federation -> service account -> GCP resource
Targets:
- GKE;
- Cloud Run;
- Artifact Registry;
- Cloud Storage;
- Terraform.
34.5 Cloud deployment checklist
Before a production cloud workflow is considered complete:
- [ ] no long-lived cloud key where OIDC can replace it;
- [ ] cloud role restricted to exact repository/ref/environment;
- [ ] production uses protected GitHub environment;
- [ ] immutable artifact/image;
- [ ] deployment serialized;
- [ ] timeout configured;
- [ ] smoke/health verification;
- [ ] logs and deployment record;
- [ ] rollback path;
- [ ] least-privilege cloud IAM;
- [ ] private-resource networking solved explicitly;
- [ ] third-party actions pinned.
35. ADVANCED — Kubernetes CI/CD
GitHub Actions is commonly used to build immutable images and then deploy them to Kubernetes using kubectl, Helm, Kustomize, a GitOps system, or a cloud-specific deployment action.
35.1 Deployment model
flowchart LR
C[Code] --> T[Test]
T --> B[Build image]
B --> R[Container registry]
R --> D[Deploy manifest/Helm]
D --> K[Kubernetes]
K --> V[Rollout verification]
V --> S[Smoke tests]
35.2 Authentication
Do not store a long-lived administrator kubeconfig in GitHub secrets if a stronger identity model is available.
Preferred patterns include:
- cloud OIDC -> cloud IAM -> cluster authentication;
- short-lived Kubernetes tokens;
- workload identity;
- GitOps pull model;
- tightly scoped service accounts.
35.3 kubectl deployment
- name: Deploy
run: |
kubectl -n production set image \
deployment/catalog \
catalog="$IMAGE@$DIGEST"
kubectl -n production rollout status \
deployment/catalog \
--timeout=5m
Use image digests when possible.
35.4 Helm
- name: Helm upgrade
run: |
helm upgrade --install catalog ./charts/catalog \
--namespace production \
--create-namespace \
--set image.repository="$IMAGE_REPOSITORY" \
--set image.digest="$IMAGE_DIGEST" \
--wait \
--timeout 5m
35.5 Kustomize
Typical flow:
kustomize build overlays/production | kubectl apply -f -
For GitOps, update an environment repository rather than applying directly.
35.6 Namespace isolation
Separate environments:
dev
staging
uat
production
But namespaces alone are not always enough isolation. Consider:
- separate clusters/accounts/subscriptions;
- network policies;
- RBAC;
- secret boundaries;
- admission policy;
- workload identity.
35.7 EKS
A secure EKS pattern:
flowchart LR
G[GitHub job] --> O[GitHub OIDC]
O --> I[AWS IAM role]
I --> E[EKS authentication]
G --> R[ECR]
E --> K[EKS API]
Private cluster endpoint:
GitHub-hosted public runner -> cannot directly reach private endpoint
Possible solution:
ephemeral self-hosted runner / ARC inside VPC -> private EKS endpoint
35.8 AKS / GKE
Use Azure/GCP federated identity to obtain short-lived credentials, then obtain cluster credentials with the narrowest required scope.
35.9 Rollback
Kubernetes rollback depends on deployment model.
Imperative example:
kubectl rollout undo deployment/catalog -n production
Helm:
helm rollback catalog <revision> -n production
GitOps:
revert environment commit -> reconciler restores prior desired state
35.10 Deployment verification
A deployment is not complete when kubectl apply returns zero.
Verify:
- rollout completed;
- desired replicas available;
- readiness healthy;
- error rate stable;
- smoke test passes;
- critical dependencies healthy.
36. ADVANCED — Infrastructure as Code
IaC workflows deserve stricter controls than ordinary application CI because they can alter networks, IAM, databases, and production resources.
36.1 Terraform pipeline
flowchart LR
C[Change] --> F[fmt]
F --> V[validate]
V --> S[Security/policy]
S --> P[plan]
P --> A[(Plan artifact)]
A --> G{Approval}
G --> X[apply]
36.2 Pull-request plan
name: Terraform PR
on:
pull_request:
paths:
- "infra/**"
- ".github/workflows/terraform-pr.yml"
permissions:
contents: read
id-token: write
jobs:
plan:
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: infra
steps:
- uses: actions/checkout@v6
- name: Configure cloud credentials
run: echo "Use provider-specific OIDC setup here"
- name: Terraform fmt
run: terraform fmt -check -recursive
- name: Terraform init
run: terraform init -input=false
- name: Terraform validate
run: terraform validate
- name: Terraform plan
run: terraform plan -input=false -out=tfplan
- name: Upload plan
uses: actions/upload-artifact@v4
with:
name: tfplan-${{ github.sha }}
path: infra/tfplan
retention-days: 5
36.3 Production apply
Production apply should normally require:
- trusted source revision;
- protected environment;
- short-lived cloud identity;
- state locking;
- serialized execution;
- reviewed plan;
- timeout;
- audit trail.
jobs:
apply:
environment: production
concurrency:
group: terraform-production
queue: max
permissions:
contents: read
id-token: write
runs-on: self-hosted
steps:
- ...
36.4 Plan artifact caution
A saved Terraform plan can contain sensitive values depending on provider/resource behavior.
Treat plan artifacts as potentially sensitive:
- short retention;
- restricted repository access;
- do not publish publicly;
- avoid logging full plan if it exposes secrets.
36.5 Terraform Cloud
Two patterns:
GitHub Actions -> Terraform Cloud API -> remote execution
or:
GitHub Actions runner -> Terraform CLI -> Terraform Cloud remote state/execution
Use the model that fits governance and network access.
36.6 OpenTofu
Most workflow architecture is similar:
fmt -> init -> validate -> plan -> gate -> apply
Use OpenTofu-specific CLI/provider/module compatibility guidance.
36.7 CloudFormation / CDK
Recommended flow:
lint/synth
validate
security/policy scan
change set
review
execute
verify
36.8 Pulumi
Use stack-specific credentials/state and protected environments.
36.9 Drift detection
Scheduled workflow:
on:
schedule:
- cron: "30 1 * * *"
The job should report drift, not automatically destroy/recreate production infrastructure unless that behavior is intentionally governed.
37. ADVANCED — Self-Hosted Runner Architecture
A self-hosted runner fleet is infrastructure, not merely “a VM with the runner installed.”
37.1 Fleet goals
A production fleet should address:
- trust boundaries;
- elasticity;
- image management;
- patching;
- credentials;
- networking;
- observability;
- cleanup;
- cost;
- disaster recovery.
37.2 Dedicated vs shared
Dedicated:
one repository/team -> one runner pool
Shared:
many repositories -> common pool
Shared fleets have better utilization but a larger trust boundary.
37.3 Reference architecture
flowchart TB
G[GitHub Actions] --> RG[Runner group]
RG --> Q[Job queue]
Q --> A1[Ephemeral runner A]
Q --> A2[Ephemeral runner B]
Q --> A3[Ephemeral runner C]
subgraph PrivateNetwork[Private network]
A1
A2
A3
P[Private package registry]
K[Kubernetes API]
DB[Private database]
end
A1 --> P
A2 --> K
A3 --> DB
37.4 Golden images
A runner image can preinstall:
- language runtimes;
- Terraform/OpenTofu;
- cloud CLIs;
- kubectl/Helm;
- organization CA certificates;
- security tools.
Benefits:
- faster jobs;
- controlled versions;
- repeatability.
Risks:
- stale image;
- vulnerable tooling;
- snowflake modifications.
Version images immutably:
runner-2026.09.1
runner-2026.09.2
37.5 Just-in-time / ephemeral registration
A mature autoscaled system registers runners when capacity is needed and destroys them after one job.
Lifecycle:
flowchart LR
Q[Job demand] --> P[Provision compute]
P --> R[Register ephemeral runner]
R --> J[Execute one job]
J --> U[Upload logs/results]
U --> D[Destroy compute]
37.6 Network access
Define egress intentionally.
Examples:
general CI pool:
internet + package registries
internal CI pool:
internal services + controlled internet
production deploy pool:
production APIs + artifact registry
no arbitrary repository access
37.7 Cloud metadata risk
If a self-hosted runner is on a cloud VM with an instance profile/managed identity, workflow code may be able to obtain those credentials.
Do not rely on “the workflow does not know the credential” as a security boundary.
Prefer:
- no ambient privileged role;
- short-lived explicit OIDC identity;
- metadata restrictions;
- isolated ephemeral compute.
37.8 Runner monitoring
Monitor:
- registered count;
- online/offline;
- busy/idle;
- queue time;
- startup latency;
- job duration;
- failure rate;
- disk;
- CPU/memory;
- runner version;
- image version;
- orphaned runners.
38. ADVANCED — Actions Runner Controller (ARC)
Actions Runner Controller is GitHub’s Kubernetes-oriented runner orchestration solution.
38.1 Mental model
flowchart LR
G[GitHub Actions] <--> L[ARC listener]
L --> C[Scale set controller]
C --> K[Kubernetes API]
K --> P1[Ephemeral runner pod]
K --> P2[Ephemeral runner pod]
K --> P3[Ephemeral runner pod]
A runner scale set represents a scalable pool of compatible runners.
38.2 Core components
Typical concepts include:
- ARC/controller;
- autoscaling runner scale set;
- listener;
- runner pods;
- Kubernetes namespace;
- authentication to GitHub;
- runner image;
- min/max runners.
38.3 Why ARC?
Use ARC when:
- Kubernetes is already an operational platform;
- workloads need private-network access;
- runners should be ephemeral;
- demand changes dynamically;
- teams need isolated pools;
- custom images are useful.
38.4 High-level installation flow
1. Prepare Kubernetes cluster.
2. Install ARC controller/chart.
3. Configure GitHub App/PAT authentication as supported.
4. Create runner scale set.
5. Set runner group/access policy.
6. Configure runner image and container mode.
7. Test job routing.
8. Add monitoring and upgrades.
Use the exact Helm chart/API versions from the current GitHub ARC documentation; ARC configuration evolves.
38.5 GitHub App vs PAT
For organization-scale production, GitHub App authentication is generally easier to govern and rotate than a broad personal token.
Evaluate:
- permissions;
- installation scope;
- organization policy;
- secret storage;
- rotation;
- rate limits.
38.6 Runner namespace isolation
Possible model:
arc-system
runners-general
runners-infra
runners-production
Use Kubernetes RBAC, network policies, pod security, node pools, and cloud identities to separate trust levels.
38.7 Custom runner image
A runner image should be reproducible.
FROM <supported-runner-base>
# Install pinned organization tools.
# Avoid baking long-lived secrets into the image.
Keep sensitive credentials external.
38.8 Docker-in-Docker
DinD can be useful for image builds but introduces:
- privilege concerns;
- storage overhead;
- daemon lifecycle;
- cache management.
Alternatives include:
- BuildKit;
- rootless builders;
- Kubernetes-native build systems;
- remote builders.
Choose based on your security model.
38.9 Kubernetes container mode
ARC can support container-oriented job execution patterns depending on the runner scale-set configuration.
Test:
- filesystem behavior;
- service containers;
- Docker requirements;
- volume access;
- network policies.
38.10 Autoscaling
Set minimum and maximum based on:
expected concurrency
startup latency
cluster capacity
budget
burst tolerance
A pool with minRunners: 0 may scale to zero but has cold-start latency.
A nonzero minimum reduces queue latency at idle cost.
38.11 ARC observability
Track:
queued GitHub jobs
desired runners
active runner pods
pod startup time
pod failures
scheduling failures
listener health
controller reconciliation
Kubernetes resource pressure
38.12 ARC security
- use ephemeral runner pods;
- separate public/untrusted and privileged pools;
- restrict pod service accounts;
- restrict network access;
- do not mount cluster-admin credentials;
- avoid privileged Docker socket exposure when possible;
- apply resource limits;
- use dedicated nodes for sensitive pools if required;
- keep ARC and runner images patched.
39. ADVANCED — Workflow Performance
Fast feedback changes developer behavior. Slow CI is not merely inconvenient.
39.1 Measure first
Important timings:
queue time
runner startup
checkout
dependency installation
build
tests
artifact upload
deployment
39.2 Critical path
flowchart LR
A[Checkout 10s] --> B[Install 60s]
B --> C[Build 90s]
B --> D[Unit 45s]
B --> E[Lint 20s]
C --> F[Integration 120s]
D --> F
E --> F
Optimizing a 20-second lint that is not on the critical path may not improve total latency.
39.3 Parallelize independent jobs
Instead of:
lint -> unit -> security -> build
use:
/-> lint
setup -> unit
\-> security
\-> build
when the tasks are truly independent.
39.4 Cache dependencies
Cache package manager download caches, not blindly the entire installed dependency directory.
Examples:
- npm cache;
- pip cache;
- Maven local repository;
- Gradle cache.
39.5 Checkout optimization
If full history is unnecessary, default shallow checkout is efficient.
For special cases:
with:
fetch-depth: 0
only when history/tags are needed.
Sparse checkout can reduce large monorepo I/O.
39.6 Selective execution
Use:
- trigger
paths; - changed-component detection;
- dynamic matrices;
- test-impact analysis.
Do not skip cross-cutting validation that protects shared code merely to gain speed.
39.7 Runner sizing
A bigger runner helps only when the task can use the resources.
Measure:
CPU saturation?
memory pressure?
disk I/O?
network?
single-threaded bottleneck?
39.8 Image pre-baking
Self-hosted/larger custom images can preinstall heavyweight tools to reduce repeated setup.
Balance speed against image maintenance complexity.
40. ADVANCED — Cost Optimization
Actions cost is a combination of compute time, storage, and engineering overhead.
40.1 Cost equation
Simplified:
CI cost =
workflow frequency
× jobs per run
× average duration
× runner rate
+ artifact/cache storage
+ self-hosted infrastructure/operations
40.2 Biggest levers
- cancel stale PR runs;
- avoid unnecessary trigger events;
- reduce matrix permutations;
- cache expensive dependencies;
- parallelize to reduce wall clock—but understand total compute may stay equal or increase;
- lower artifact retention;
- use appropriate runner sizes;
- run expensive scans at the right cadence;
- use path-aware monorepo workflows;
- measure before migrating to self-hosted merely for cost.
40.3 Matrix cost explosion
This:
os: [ubuntu, windows, macos]
node: [20, 22, 24]
db: [postgres, mysql, sqlite]
creates:
3 × 3 × 3 = 27 jobs
Ask whether every combination provides unique value.
40.4 Cost-aware compatibility strategy
PR:
primary OS + supported runtime versions
Nightly:
full OS/runtime compatibility matrix
Release:
all required release targets
40.5 Self-hosted economics
Self-hosted is not “free.”
Include:
- compute;
- idle capacity;
- Kubernetes cluster;
- storage;
- network;
- image building;
- patching;
- security;
- on-call;
- monitoring;
- autoscaling engineering.
41. ADVANCED — Monorepo Workflows
Monorepos require selective execution without losing confidence in shared dependencies.
41.1 Naive model
any file changed -> build every service
Simple but expensive.
41.2 Path-filter model
on:
pull_request:
paths:
- "services/api/**"
- "packages/shared/**"
Good for independent components but limited when dependencies are complex.
41.3 Changed-component detection
flowchart LR
D[Git diff] --> C[Changed files]
C --> M[Map files -> components]
M --> G[Expand dependency graph]
G --> J[Dynamic matrix]
J --> B[Build affected components]
41.4 Dynamic component matrix
Prepare job:
outputs:
matrix: ${{ steps.detect.outputs.matrix }}
Example output:
{
"component": ["api", "worker"]
}
Build:
strategy:
matrix: ${{ fromJSON(needs.detect.outputs.matrix) }}
41.5 Shared library impact
If:
services/api -> packages/core
services/worker -> packages/core
a change to packages/core should build/test both services.
Path filters alone cannot fully represent a dependency graph unless manually encoded.
41.6 Independent deployments
api change -> deploy API
frontend change -> deploy frontend
shared change -> deploy affected services
Each component can have its own:
- artifact;
- image;
- version;
- environment;
- concurrency group.
41.7 Monorepo cache keys
Include component identity:
key: npm-${{ matrix.component }}-${{ hashFiles(format('{0}/package-lock.json', matrix.component)) }}
Avoid one giant cache that creates contention and invalidates constantly.
42. ADVANCED — Multi-Repository Workflows
Organizations often centralize CI policy while keeping application code in separate repositories.
42.1 Patterns
Central reusable workflows
flowchart TB
A[service-a] --> C[central-workflows]
B[service-b] --> C
D[service-c] --> C
Cross-repository dispatch
Repo A -> API / repository_dispatch -> Repo B workflow
Central deployment repository
app repositories -> build artifacts
deployment repository -> environment promotion
42.2 repository_dispatch
Caller using GitHub CLI/API conceptually:
gh api \
--method POST \
repos/my-org/deployer/dispatches \
-f event_type=deploy-request \
-f 'client_payload[service]=catalog' \
-f 'client_payload[version]=2.4.1'
Receiver:
on:
repository_dispatch:
types: [deploy-request]
42.3 Authentication
GITHUB_TOKEN is repository-scoped and is intentionally limited for cross-repository actions.
Use an appropriately scoped:
- GitHub App installation token;
- fine-grained PAT;
- supported organization/reusable-workflow access policy.
Prefer GitHub Apps for machine-to-machine organization automation when practical.
42.4 Cross-repo artifacts
Common solutions:
- package registry;
- container registry;
- release assets;
- object storage;
- APIs.
Avoid fragile “download another repo’s workflow artifact by guessing its latest run” designs unless the run identity is deterministic and verified.
43. ADVANCED — Workflow Chaining
43.1 workflow_run
Use chaining when workflows need different privileges.
on:
workflow_run:
workflows: ["Build"]
types: [completed]
43.2 Security boundary
A strong pattern:
flowchart LR
A[PR workflow<br/>untrusted, read-only] --> B[Artifact/result]
B --> C[workflow_run<br/>trusted workflow]
C --> D[Privileged publish/comment]
But the privileged workflow must validate every artifact/input created by the untrusted workflow.
43.3 Avoid excessive chains
Bad:
workflow A
-> B
-> C
-> D
-> E
-> F
Problems:
- harder traceability;
- fragmented logs;
- latency;
- failure handling complexity;
- privilege confusion.
Prefer one workflow with job dependencies when a single trust boundary is sufficient.
44. ADVANCED — GitHub API Integration
GitHub Actions can automate GitHub itself using REST, GraphQL, gh, Octokit, or github-script.
44.1 gh
- name: Comment on issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh issue comment "${{ github.event.issue.number }}" \
--body "Automation completed."
44.2 gh api
- name: Read repository
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh api "repos/${GITHUB_REPOSITORY}"
44.3 actions/github-script
- uses: actions/github-script@<PINNED_SHA>
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: 'CI completed.'
})
44.4 REST vs GraphQL
| REST | GraphQL |
|---|---|
| simple resource operations | query related data efficiently |
| familiar endpoints | select exact fields |
easy with gh api | useful for complex org/report queries |
| pagination by endpoint | query cost model |
44.5 Checks and status APIs
Use when building custom CI integrations, but ordinary Actions jobs already produce checks automatically.
44.6 Actions API
Useful automation:
- list runs;
- re-run jobs;
- cancel runs;
- download logs;
- manage artifacts/caches;
- inspect runners.
44.7 Rate limits
Large organizations should design API automation with:
- pagination;
- conditional requests;
- caching;
- backoff;
- GitHub App installation tokens;
- rate-limit monitoring.
45. ADVANCED — Repository Governance
Workflows are enforceable only when repository governance requires them.
45.1 Governance layers
flowchart TB
R[Repository rules/rulesets] --> B[Branch/tag policy]
R --> S[Required status checks]
R --> M[Merge policy]
R --> C[CODEOWNERS/reviews]
R --> A[Actions settings]
R --> E[Environment protections]
45.2 Branch protection / rulesets
Typical main-branch policy:
- require PR;
- require approvals;
- require code-owner review;
- require status checks;
- dismiss stale approvals if desired;
- block force pushes;
- restrict deletion;
- require signed commits where policy demands;
- require linear history if desired;
- merge queue for high-volume repositories.
45.3 Required status checks
Names must remain stable enough for rules to target them.
Good:
jobs:
unit:
name: Unit Tests
Do not casually rename required jobs across hundreds of repositories without updating governance.
45.4 Merge queue and merge_group
Repositories using merge queues should ensure required CI runs against the merge group event.
Example:
on:
pull_request:
merge_group:
Without merge-group support, a required workflow may not validate the exact queued merge combination.
45.5 CODEOWNERS
CODEOWNERS decides review ownership. Actions can support it but should not replace branch/ruleset enforcement with homemade scripts.
45.6 Actions repository settings
Govern:
- allowed actions;
- workflow permissions;
- fork workflow policy;
- reusable workflow access;
- runner access.
45.7 Environments as governance
Production controls belong in both:
repository source rules
+
environment deployment rules
A merge approval and a deployment approval answer different questions.
46. ADVANCED — Organization Governance
Organization-level policy reduces inconsistent security across repositories.
46.1 Policy areas
- allowed GitHub Actions;
- third-party action restrictions;
- SHA pinning policies where available;
- reusable workflow access;
- organization secrets;
- organization variables;
- runner groups;
- workflow permissions;
- retention;
- fork workflow policy.
46.2 Organization action policy
Possible strategy:
Allow:
GitHub-owned actions
selected verified actions
organization-owned actions
explicitly reviewed third-party actions
This is stronger than letting every repository use arbitrary Marketplace code.
46.3 Organization secrets
Use org secrets for truly shared credentials, with repository access limited.
Do not put a powerful enterprise credential into an organization secret visible to all repositories merely for convenience.
46.4 Organization variables
Good for non-sensitive standards:
DEFAULT_AWS_REGION
ARTIFACT_RETENTION_DAYS
INTERNAL_REGISTRY
PLATFORM_TEAM
46.5 Runner groups
Example:
general-linux
mobile-macos
internal-network
production-deploy
terraform-private
Grant repositories to groups intentionally.
46.6 Central workflow governance
A platform repository can own:
reusable-ci.yml
container-build.yml
terraform-plan.yml
terraform-apply.yml
deploy-kubernetes.yml
security-scan.yml
Application repositories call reviewed versions.
This creates a reusable paved road without copying thousands of lines of YAML.
46.7 Governance principle
Centralize policy and common capability, not every repository-specific decision.
A reusable workflow should expose intentional inputs rather than becoming a 100-input universal pipeline.
47. ADVANCED — Enterprise Governance
Enterprise governance applies policy consistently across organizations while preserving controlled delegation.
47.1 Enterprise control plane
flowchart TB
E[Enterprise policy] --> O1[Organization A]
E --> O2[Organization B]
E --> O3[Organization C]
O1 --> R1[Repositories]
O2 --> R2[Repositories]
O3 --> R3[Repositories]
E --> RG[Enterprise runner groups]
E --> AU[Audit / usage]
47.2 Enterprise policy areas
Common controls:
- Actions usage policy;
- allowed/selected actions;
- enterprise runner groups;
- organization inheritance;
- workflow permissions;
- fork policies;
- reusable workflow strategy;
- audit logging;
- billing/usage analysis;
- identity and access.
47.3 Policy inheritance
A lower level should not be able to silently weaken an enterprise security requirement.
Think in layers:
Enterprise maximum allowed capability
↓
Organization policy
↓
Repository policy
↓
Workflow/job least privilege
A repository can usually choose to be more restrictive, but should not be able to exceed organization/enterprise boundaries.
47.4 Enterprise runner groups
Useful model:
Enterprise runner groups
├── general
├── regulated
├── production
├── mobile
└── high-memory
Each organization gets access only to the groups it needs.
47.5 Audit logs
Audit:
- workflow policy changes;
- runner changes;
- secret/configuration changes;
- repository settings;
- environment approvals;
- organization/enterprise Actions settings;
- authentication events.
Audit data is useful only when retained, searchable, and connected to incident response.
47.6 Enterprise operating model
A mature model usually separates:
| Role | Responsibility |
|---|---|
| Enterprise admins | platform-wide policy |
| Platform team | reusable workflows/runners |
| Security | controls, review, detection |
| Org owners | organization policy |
| Repo maintainers | repository CI/CD |
| App teams | application-specific logic |
| Release approvers | production authorization |
48. ADVANCED — Workflow Execution Policies
Execution policy answers:
Which workflows may execute, under which identities, events, repositories, and protections?
48.1 Policy dimensions
- actor;
- event;
- repository;
- action source;
- runner group;
- permissions;
- environment;
- fork status;
- branch/tag.
48.2 Defense in depth
flowchart LR
E[Event] --> P1[Enterprise/Org policy]
P1 --> P2[Repository Actions policy]
P2 --> W[Workflow trigger/conditions]
W --> T[Token permissions]
T --> R[Runner access]
R --> G[Environment gate]
G --> X[Privileged action]
No single layer should be expected to carry all authorization.
48.3 Actor restrictions
Avoid implementing sensitive authorization as:
if: github.actor == 'alice'
This is brittle and not a substitute for:
- repository permissions;
- teams;
- environment reviewers;
- GitHub Apps;
- protected branches;
- cloud IAM.
Actor checks can supplement, not replace, policy.
48.4 Event restrictions
Examples:
PR -> test only
push main -> build/publish candidate
release -> publish release
workflow_dispatch -> controlled operational action
schedule -> maintenance
Tie higher privileges to trusted events and protected refs.
49. ADVANCED — Metrics and Observability
A CI/CD platform should be operated with SLO-like thinking.
49.1 Core metrics
Workflow metrics
run count
success rate
failure rate
cancellation rate
duration
queue duration
re-run rate
Job metrics
duration by job
failure by job
runner wait time
matrix expansion
timeout count
Runner metrics
online runners
busy runners
utilization
queue depth
startup latency
job pickup latency
failure rate
version/image drift
Cost metrics
minutes by repository
minutes by runner type
artifact storage
cache storage
top expensive workflows
49.2 CI service-level indicators
Example internal objectives:
| SLI | Example target |
|---|---|
| PR CI p50 | < 5 min |
| PR CI p95 | < 15 min |
| Queue p95 | < 2 min |
| Platform-caused failure | < 1% |
| Production deployment success | > 99% |
| Runner availability | > 99.9% |
These are examples, not universal targets.
49.3 Failure classification
Do not treat all red workflows the same.
Classify:
code defect
test defect/flaky test
workflow defect
runner/platform
dependency registry
network
cloud provider
permission/policy
deployment target
Without classification, teams optimize the wrong problem.
49.4 Observability dashboard
flowchart TB
A[Actions APIs / audit logs] --> D[Data pipeline]
R[Runner metrics] --> D
B[Billing/usage] --> D
D --> M[Metrics store]
M --> V[Dashboard]
M --> AL[Alerts]
49.5 Alerting
Good alerts indicate action:
runner pool unable to accept jobs
production deployment failure
queue time above SLO
reusable workflow failure spike
authentication failures
Bad alerts notify on every ordinary developer test failure to a central on-call.
50. ADVANCED — Workflow Notifications
Notifications should route information to the people who can act.
50.1 Native notifications
GitHub can notify users through:
- web;
- email;
- repository/activity notification mechanisms.
50.2 Chat notifications
Possible integrations:
- Slack;
- Microsoft Teams;
- custom webhook;
- incident-management tool.
50.3 Notification job
notify:
needs: [build, test, deploy]
if: failure()
runs-on: ubuntu-latest
steps:
- name: Build notification payload
env:
RUN_URL: >-
${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
echo "Workflow failed: $RUN_URL"
Send via a reviewed integration or webhook.
50.4 Avoid secret webhooks when apps can use scoped auth
A long-lived chat webhook is a bearer credential. Store and scope it appropriately.
50.5 Notification design
PR failure:
GitHub check is usually enough
Production failure:
GitHub + deployment channel + incident path if impact exists
Scheduled maintenance failure:
team-owned alert
51. ADVANCED — Workflow Cancellation
Cancellation is a normal workflow state and should be designed for.
51.1 Cancellation sources
- user clicks Cancel;
- concurrency cancels older run;
- API cancellation;
- timeout;
- organization/platform policy.
51.2 Concurrency cancellation
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
51.3 Cleanup
If a workflow creates temporary infrastructure, design cleanup even when cancellation occurs.
Options:
- action
postphase; - cleanup step/job;
- TTL on temporary resources;
- external janitor workflow;
- idempotent resource naming.
51.4 Preview environment example
flowchart LR
P[PR opened/update] --> C[Create/update preview]
P --> X[Older run cancelled]
X --> J[Cleanup/TTL safety]
Q[PR closed] --> D[Destroy preview]
Never rely on only the “PR closed” workflow for cleanup. Events can fail; build a TTL/janitor.
51.5 Signals
Processes should handle termination where practical.
Containerized/server steps should not require an unbounded graceful shutdown.
52. ADVANCED — Pull Request CI Patterns
PR CI is the highest-volume and often lowest-trust workflow surface.
52.1 Baseline PR workflow
name: PR CI
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
permissions:
contents: read
cache-mode: read
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
ci:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- run: ./ci.sh
52.2 Draft PRs
Options:
- skip expensive tests until ready;
- run only lint/unit;
- run full CI if draft PRs are routinely used for collaboration.
Make behavior explicit.
52.3 Fork PRs
Assume:
- code is untrusted;
- repository secrets are not available;
- write token is restricted;
- self-hosted privileged runners are dangerous.
52.4 PR comments
Do not switch the whole test workflow to pull_request_target merely to post a comment.
Safer pattern:
pull_request -> test -> artifact/result
workflow_run -> trusted commenter
Validate the result artifact before use.
52.5 Changed-file workflows
Use path filtering for coarse optimization and dependency-aware detection for complex monorepos.
52.6 Preview environments
A secure preview system needs:
- isolated namespace/account;
- no production secrets;
- bounded lifetime;
- safe ingress;
- predictable DNS;
- cleanup;
- cost limit;
- untrusted input handling.
52.7 Merge queue
If required checks are used with merge queue:
on:
pull_request:
merge_group:
Validate the queued merge group, not only the original PR head.
53. ADVANCED — Branch and Release Strategies
GitHub Actions should implement your source-control/release strategy, not accidentally define one.
53.1 Trunk-based development
feature branch -> PR -> main -> deploy
Good fit for:
- small changes;
- strong automation;
- feature flags;
- frequent release.
53.2 GitFlow-style
feature -> develop -> release -> main
\-> hotfix
More branches and workflow states mean more automation complexity.
Use only if the organizational release model actually needs it.
53.3 Release branches
Example:
main
release/2.4
release/2.5
Actions can validate/patch supported release lines.
53.4 Environment branches
A branch-per-environment model can work but couples source history to deployment state.
Alternative:
immutable artifact + environment promotion metadata
Often easier to reason about.
53.5 Tag-based release
on:
push:
tags:
- "v*"
Validate that the tag points to an approved commit lineage if required.
53.6 Hotfix workflow
A hotfix should still preserve:
review
tests
artifact integrity
production protection
post-deploy verification
“Emergency” should not automatically mean “no controls.”
54. ADVANCED — Deployment Strategies
54.1 Rolling deployment
Replace instances gradually.
flowchart LR
O[Old replicas] --> M[Mixed old/new]
M --> N[All new]
Pros:
- simple;
- resource efficient.
Risk:
- mixed versions coexist.
54.2 Blue/green
flowchart LR
U[Users] --> R{Router}
R --> B[Blue active]
R -. switch .-> G[Green new]
Deploy green, verify, switch traffic. Rollback by switching back while blue remains viable.
54.3 Canary
flowchart LR
U[Traffic] --> S{Traffic split}
S -->|95%| O[Old]
S -->|5%| N[New]
N --> M[Metrics]
M -->|Healthy| I[Increase traffic]
M -->|Bad| R[Rollback]
Requires observability and traffic control.
54.4 Progressive delivery
Canary is one form. A progression can use:
1% -> 5% -> 25% -> 50% -> 100%
Each stage can gate on:
- error rate;
- latency;
- saturation;
- business metric;
- manual approval.
54.5 Feature flags
Deployment and release become separate:
deploy code disabled
-> enable feature for internal users
-> 1%
-> 10%
-> all
Flags need lifecycle management; stale flags are operational debt.
54.6 Immutable deployment
Promote a previously built artifact/image rather than changing it per environment.
Strong model:
same digest -> staging -> UAT -> production
54.7 Smoke tests
After deploy:
- name: Smoke
run: |
curl --fail --retry 5 --retry-delay 5 \
https://app.example.com/health
A /health endpoint alone may not represent user-critical functionality. Include targeted synthetic smoke checks when needed.
55. ADVANCED — Testing GitHub Actions
Workflows are production code and should be tested.
55.1 Validation layers
YAML parser
workflow linter
shell/script tests
custom action unit tests
reusable workflow tests
integration repository
staging deployment
production canary
55.2 actionlint
actionlint catches many:
- syntax;
- expressions;
- shellcheck-related issues;
- context mistakes.
Example local/CI use:
actionlint
55.3 YAML validation
A generic YAML parser can catch syntax but not GitHub-specific semantic errors.
Use both generic syntax validation and Actions-aware linting.
55.4 JavaScript action testing
Separate business logic from Actions runtime adapters.
Example architecture:
src/
logic.js <- pure/testable
action.js <- @actions/core wrapper
Unit test logic.js, then integration-test action.js.
55.5 Composite action testing
Create a small workflow that invokes the action with:
- default inputs;
- custom inputs;
- failing inputs;
- expected outputs;
- Linux/Windows/macOS if supported.
55.6 Reusable workflow testing
A reusable workflow should have a test caller.
test repository/workflow -> call reusable workflow -> assert outputs/artifacts
55.7 Local emulation with act
act can be useful for fast local feedback but is not an exact GitHub-hosted runner implementation.
Differences may include:
- images;
- permissions;
- networking;
- services;
- GitHub API behavior;
- environment protection;
- OIDC;
- hosted-runner tooling.
Use it as a development aid, not final proof.
55.8 Staging workflow tests
For risky platform changes:
branch/ref of reusable workflow
-> test repository
-> staging environment
-> production version tag
Do not immediately move a central v1 tag across hundreds of repositories without canary validation.
56. ADVANCED — Workflow Maintenance
A workflow that was secure and correct two years ago may no longer be.
56.1 Maintenance inventory
Review:
- action versions;
- deprecated actions;
- runner images;
- runtime versions;
- third-party dependencies;
- secrets;
- OIDC trust;
- runner application;
- container images;
- reusable workflow versions.
56.2 Dependabot for GitHub Actions
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
Use review and test before merging updates.
56.3 Runner image changes
Avoid relying on accidental preinstalled software.
Explicitly set up critical versions:
- uses: actions/setup-node@v7
with:
node-version: "24"
56.4 Node runtime changes for actions
JavaScript actions target runner-supported Node action runtimes.
When GitHub deprecates an action runtime, update custom actions and third-party dependencies.
56.5 Refactoring triggers
Refactor when you see:
- copy/paste across repositories;
- 500-line monolithic workflows;
- duplicated setup steps;
- unclear privilege boundaries;
- repeated shell scripts embedded in YAML;
- enormous boolean conditions.
56.6 Secret rotation
Track:
owner
purpose
created
last rotated
expires
repositories
replacement plan
Prefer OIDC to reduce long-lived cloud secrets.
56.7 Documentation
Each production workflow should make it easy to answer:
What triggers this?
What does it change?
What identity does it use?
What is the artifact?
What protects production?
How do I retry?
How do I roll back?
Who owns it?
57. ADVANCED — GitHub Actions Limits
Limits change over time. Build scalable systems from current GitHub documentation rather than memorizing an old blog.
57.1 Important current examples
As of this handbook verification date, GitHub documents limits including:
- workflow-run lifetime;
- environment approval waiting;
- matrix expansion;
- concurrency/queue behavior;
- reusable workflow depth/count;
- API limits;
- runner/job limits;
- storage limits.
Matrix
A matrix can generate at most 256 jobs per workflow run in the current documented limit.
Workflow run
Current documentation lists a workflow run maximum lifetime of 35 days, including execution and waiting/approval time.
Environment approval
A workflow can wait for environment approval for up to 30 days under the current documented limit.
Reusable workflows
Current reference documents:
up to 10 levels
up to 50 unique reusable workflows reachable from one caller
57.2 Design principle
Do not design at the exact hard limit.
If you need:
250 matrix jobs every PR
the issue is probably architectural before it is a quota issue.
57.3 Common scale failures
- giant dynamic matrix;
- thousands of repos calling one bottleneck service;
- API polling without backoff;
- artifact retention explosion;
- excessive cross-workflow chaining;
- too-small runner pool;
- limited private endpoint capacity.
58. ADVANCED — GitHub Actions Importer
GitHub Actions Importer helps migrate CI/CD definitions from supported systems.
58.1 Migration is not translation only
flowchart LR
A[Existing CI] --> I[Inventory]
I --> C[Convert]
C --> R[Review]
R --> T[Test]
T --> S[Security redesign]
S --> P[Production cutover]
A converted workflow can be syntactically correct but architecturally poor.
58.2 Typical migration sources
Examples include:
- Jenkins;
- GitLab CI/CD;
- CircleCI;
- Azure DevOps;
- Travis CI.
58.3 Migration inventory
Record:
pipeline trigger
secrets
service connections
agents/runners
artifacts
caches
environments
approvals
shared libraries
plugins
notifications
deployment targets
58.4 Replace platform-specific concepts
Examples:
Jenkins shared library -> reusable workflow/composite action
Jenkins agent label -> runs-on labels/runner group
GitLab artifact -> Actions artifact
Azure environment approval -> GitHub environment protection
CI cloud service connection -> OIDC + cloud IAM
58.5 Migration security opportunity
Do not copy:
- long-lived cloud keys;
- administrator tokens;
- shared mutable workers;
- insecure shell interpolation;
- unpinned third-party code.
Use migration as a security modernization.
59. ADVANCED — Common CI/CD Design Patterns
59.1 Build-test-deploy
flowchart LR
B[Build] --> T[Test] --> D[Deploy]
Simple and appropriate for many applications.
59.2 Build once, deploy many
flowchart LR
C[Commit] --> B[Build]
B --> A[(Artifact)]
A --> S[Stage]
S --> U[UAT]
U --> P[Prod]
Recommended for immutable promotion.
59.3 Fan-out/fan-in
flowchart LR
B[Build] --> U[Unit]
B --> I[Integration]
B --> S[Security]
U --> G[Gate]
I --> G
S --> G
59.4 Matrix
One definition, many permutations.
Good for compatibility testing.
59.5 Central reusable pipeline
application repo
-> central reusable CI
-> central security
-> central deployment
Useful for consistent organization standards.
59.6 Artifact promotion
Do not rebuild between environments.
Promote:
artifact ID
image digest
release bundle
59.7 Conditional deployment
if: github.ref == 'refs/heads/main'
Use conditions plus environment protections.
59.8 Manual approval gates
Human approval is appropriate when:
- regulatory control;
- production change review;
- high-risk operation;
- business timing decision.
It is not a substitute for automated verification.
59.9 Event-driven automation
release created -> publish
issue labeled -> triage
deployment status -> notify
package published -> downstream test
59.10 Dynamic pipeline generation
GitHub workflow structure itself remains YAML-defined, but matrices and outputs can dynamically choose work.
Avoid generating arbitrary executable workflow logic from untrusted input.
60. ADVANCED — GitHub Actions Anti-Patterns
60.1 Duplicate workflow logic
Problem:
50 repositories × copied 200-line workflow
Fix:
reusable workflow + small caller
60.2 Overprivileged token
Bad:
permissions: write-all
Fix:
permissions:
contents: read
and elevate only specific jobs.
60.3 Long-lived cloud credentials
Bad:
AWS_ACCESS_KEY_ID secret
AWS_SECRET_ACCESS_KEY secret
when OIDC is supported.
Fix:
OIDC -> scoped role -> short-lived token
60.4 Mutable third-party actions
Bad:
uses: unknown/action@main
Better:
uses: unknown/action@<reviewed-full-SHA>
60.5 Secrets in YAML
Never:
env:
PASSWORD: real-password-here
60.6 Secrets in logs
Never enable shell tracing around secret-bearing commands without understanding exposure.
Avoid:
set -x
for secret-heavy scripts.
60.7 Untrusted input in shell
Bad:
run: echo "${{ github.event.issue.title }}"
Use environment indirection or safe structured code.
60.8 Unsafe pull_request_target
Never combine:
privileged base context
+
checkout attacker-controlled PR
+
execute PR code
60.9 Persistent public self-hosted runners
This creates a persistence and internal-network attack surface.
Use GitHub-hosted runners or isolated ephemeral runners for untrusted public workflows.
60.10 Excessive matrix
A matrix should represent valuable test dimensions, not every imaginable combination.
60.11 Cache misuse
Never make cache availability a correctness requirement.
Never let low-trust code poison privileged cache state.
60.12 Artifact misuse
Artifacts are not automatically secure secret stores.
Do not upload:
- cloud credentials;
- signing keys;
.envsecrets;- kubeconfigs;
- plaintext tokens.
60.13 Hard-coded environments
Bad:
aws s3 cp ... s3://prod-account-bucket
inside generic CI.
Use environment-specific variables and identities.
60.14 Excessive workflow chaining
Prefer job dependencies unless separate workflow trust boundaries are actually useful.
60.15 Monolithic workflow
Symptoms:
- hundreds/thousands of lines;
- unrelated products;
- mixed privilege levels;
- many nested conditions.
Split by responsibility and reuse stable building blocks.
60.16 Missing timeouts
A stuck external call can consume hours.
Set realistic job/step timeouts.
60.17 Missing concurrency
Concurrent production deployments can race.
Add environment-specific serialization.
60.18 Missing deployment protections
A YAML condition is not equivalent to protected environment policy.
61. ADVANCED — Production Best Practices
This chapter condenses the handbook into a production baseline.
61.1 Secure identity
- least-privilege
GITHUB_TOKEN; - OIDC for cloud;
- separate environment roles;
- no static cloud keys where federation works;
- protected production environments.
61.2 Supply-chain controls
- pin third-party actions by full SHA;
- automate action update PRs;
- scan dependencies;
- generate SBOM/provenance as needed;
- attest release artifacts;
- verify before deployment.
61.3 Runner controls
- GitHub-hosted for common untrusted CI;
- ephemeral self-hosted/ARC for private workloads;
- runner groups;
- network segmentation;
- patched golden images;
- no ambient privileged credentials.
61.4 Workflow engineering
- reusable workflows;
- composite actions for repeated steps;
- job timeouts;
- concurrency;
- path filtering;
- caching;
- build once/deploy many;
- clear outputs;
- readable conditions.
61.5 Repository governance
- rulesets/branch protection;
- required checks;
- merge queue support;
- CODEOWNERS;
- restricted Actions policy;
- protected environments.
61.6 Operational readiness
- workflow metrics;
- queue monitoring;
- runner monitoring;
- cost monitoring;
- failure classification;
- ownership;
- rollback;
- runbook;
- auditability.
61.7 Production reference workflow
name: Production Release
on:
push:
tags:
- "v*"
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 20
cache-mode: read
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: release-${{ github.sha }}
path: dist/
retention-days: 30
deploy:
needs: verify
permissions:
contents: read
id-token: write
environment:
name: production
url: https://app.example.com
concurrency:
group: production
queue: max
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/download-artifact@v5
with:
name: release-${{ github.sha }}
path: dist/
- name: Obtain short-lived cloud credentials
run: echo "Use provider OIDC action pinned to a reviewed SHA"
- name: Deploy
run: ./deploy.sh dist/
- name: Verify
run: ./smoke-test.sh https://app.example.com
The exact cloud/auth action should be pinned to a reviewed SHA.
62. ADVANCED — Reference and Administration
This section is a practical index of where to look when operating GitHub Actions.
62.1 Workflow reference
Use the official workflow syntax reference for:
- valid keys;
- contexts allowed in a key;
- defaults;
- concurrency;
- matrices;
- containers;
- services;
- reusable calls;
- modern syntax such as cache access and parallel/background steps.
62.2 Events reference
Check the event reference for:
- default activity types;
- branch/ref behavior;
- event payload;
- default-branch requirements;
- fork security behavior;
- recursive-trigger limitations.
62.3 Workflow commands
Use for:
- outputs;
- environment variables;
- PATH;
- step summaries;
- annotations;
- masking;
- action state.
62.4 Variables, expressions, and contexts
These three references answer different questions:
| Reference | Question |
|---|---|
| Variables | What environment/config variables exist? |
| Expressions | What operators/functions can I evaluate? |
| Contexts | What structured runtime data is available here? |
62.5 Deployments and environments
Use for:
- required reviewers;
- wait timers;
- deployment branches/tags;
- custom protection rules;
- environment variables/secrets;
- deployment records.
62.6 Runners
Administrators should know:
- standard hosted runner images;
- larger runner capabilities;
- self-hosted network requirements;
- runner groups;
- ARC;
- image/runtime updates.
62.7 Security
Always keep current on:
- secure-use reference;
pull_request_target;- secrets;
- OIDC;
- action pinning;
- fork policy;
- runner isolation;
- cache security.
62.8 Limits
Check current limits before designing:
- huge matrices;
- deep reusable workflows;
- long approvals;
- extreme run durations;
- high API volumes;
- large artifacts/caches.
62.9 Billing and usage
Review:
- included minutes;
- hosted runner rates;
- larger runner rates;
- storage;
- organization usage;
- repository hot spots.
62.10 Administrative runbook
A GitHub Actions administrator should maintain:
Actions policy
runner inventory
runner group ownership
approved actions
reusable workflow catalog
secret inventory
OIDC trust inventory
environment inventory
cost dashboard
platform SLOs
upgrade cadence
incident runbook
Part III — End-to-End Tutorials and Labs
Lab A — From Zero to a Production-Quality Node.js CI Workflow
A.1 Repository
demo-app/
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
├── test/
├── package.json
└── package-lock.json
A.2 Requirements
We want:
- PR and
mainvalidation; - least privilege;
- stale-run cancellation;
- Node 22 and 24 compatibility;
- npm cache;
- lint;
- test;
- build;
- artifact;
- clear summary.
A.3 Workflow
name: Application CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
cache-mode: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
name: Node ${{ matrix.node }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
node: [22, 24]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}
cache: npm
- name: Install
run: npm ci
- name: Lint
run: npm run lint
- name: Unit tests
run: npm test -- --ci
- name: Build
run: npm run build
- name: Summary
if: always()
run: |
{
echo "## Node ${{ matrix.node }}"
echo ""
echo "- Ref: \`${{ github.ref }}\`"
echo "- SHA: \`${{ github.sha }}\`"
echo "- Result: \`${{ job.status }}\`"
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload production build
if: matrix.node == 24
uses: actions/upload-artifact@v4
with:
name: web-${{ github.sha }}
path: dist/
retention-days: 7
A.4 Why each control exists
| Control | Reason |
|---|---|
contents: read | CI should not need repository write |
cache-mode: read | PR code does not need to publish cache state |
concurrency | cancel obsolete CI |
timeout-minutes | bound failure/hang cost |
fail-fast: false | collect compatibility results |
npm ci | lockfile-respecting deterministic install |
| artifact on Node 24 only | avoid duplicate build artifacts |
Lab B — Reusable Organization CI
B.1 Goal
Ten repositories should use the same quality gate without copying the workflow.
B.2 Central workflow
name: Standard Node CI
on:
workflow_call:
inputs:
node-version:
type: string
default: "24"
working-directory:
type: string
default: "."
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version: ${{ inputs.node-version }}
cache: npm
cache-dependency-path: >-
${{ inputs.working-directory }}/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
B.3 Application caller
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
ci:
uses: my-org/platform-workflows/.github/workflows/node-ci.yml@v3
with:
node-version: "24"
B.4 Version promotion
feature branch in platform-workflows
-> test repositories
-> tag v3.1.0
-> move reviewed v3 major tag if your organization uses moving major tags
-> Dependabot/update consumers
For high-assurance consumers, pin the called workflow to a full commit SHA.
Lab C — Secure AWS OIDC Deployment
C.1 Architecture
sequenceDiagram
participant G as GitHub job
participant O as GitHub OIDC
participant S as AWS STS
participant R as IAM Role
participant A as AWS Resource
G->>O: request ID token
O-->>G: signed token
G->>S: AssumeRoleWithWebIdentity
S->>R: evaluate trust conditions
R-->>S: allow
S-->>G: temporary credentials
G->>A: deploy
C.2 AWS trust principles
The role trust should constrain:
- issuer;
- audience;
- repository;
- branch or GitHub environment;
- immutable subject format if applicable to your repository.
The permission policy should constrain the AWS resources/actions separately.
C.3 Workflow
name: Deploy Production
on:
workflow_dispatch:
inputs:
release:
type: string
required: true
permissions:
contents: read
jobs:
deploy:
environment: production
concurrency:
group: production
queue: max
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@<PINNED_SHA>
with:
role-to-assume: arn:aws:iam::123456789012:role/github-prod-deploy
aws-region: ap-northeast-1
- name: Verify identity
run: aws sts get-caller-identity
- name: Deploy exact release
env:
RELEASE: ${{ inputs.release }}
run: ./deploy-release.sh "$RELEASE"
Lab D — Build and Deploy a Container to Kubernetes
D.1 Requirements
- build once;
- push to registry;
- deploy by digest;
- production environment approval;
- rollout verification;
- smoke test.
D.2 Flow
flowchart LR
C[Commit/tag] --> B[Build image]
B --> R[(Registry)]
R --> I[Image digest]
I --> G{Production gate}
G --> H[Helm deploy]
H --> V[Rollout verify]
V --> S[Smoke test]
D.3 Skeleton workflow
jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@<PINNED_SHA>
- uses: docker/login-action@<PINNED_SHA>
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: build
uses: docker/build-push-action@<PINNED_SHA>
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
deploy:
needs: image
environment: production
concurrency:
group: production
queue: max
runs-on: self-hosted
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- name: Deploy immutable digest
env:
DIGEST: ${{ needs.image.outputs.digest }}
run: |
helm upgrade --install app ./chart \
--namespace production \
--set image.digest="$DIGEST" \
--wait \
--timeout 5m
- name: Smoke test
run: curl --fail --retry 5 https://app.example.com/health
A private Kubernetes API is a networking requirement; the self-hosted runner must be intentionally placed and secured for that trust zone.
Lab E — Terraform Plan / Approval / Apply
E.1 Safe model
flowchart LR
PR[Pull request] --> P[Plan]
P --> R[Review]
R --> M[Merge]
M --> A[Trusted apply workflow]
A --> E[Protected environment]
E --> C[Cloud]
Do not blindly reuse a plan produced by untrusted code in a privileged apply job without validating the exact source revision and trust model.
E.2 Plan job
name: Terraform Plan
on:
pull_request:
paths: ["infra/**"]
permissions:
contents: read
id-token: write
jobs:
plan:
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra
steps:
- uses: actions/checkout@v6
- run: terraform fmt -check -recursive
- run: terraform init -input=false
- run: terraform validate
- run: terraform plan -input=false
E.3 Apply job
name: Terraform Apply
on:
push:
branches: [main]
paths: ["infra/**"]
permissions:
contents: read
jobs:
apply:
environment: production
permissions:
contents: read
id-token: write
concurrency:
group: terraform-production
queue: max
runs-on: self-hosted
timeout-minutes: 30
defaults:
run:
working-directory: infra
steps:
- uses: actions/checkout@v6
- run: terraform init -input=false
- run: terraform plan -input=false -out=tfplan
- run: terraform apply -input=false tfplan
In a mature setup, policy/security checks and plan review happen before apply.
Lab F — ARC Runner Fleet for Private Infrastructure
F.1 Use case
The organization must deploy to:
- private Kubernetes API;
- internal artifact registry;
- private database migration endpoint.
GitHub-hosted runners cannot directly reach the resources.
F.2 Architecture
flowchart TB
G[GitHub Actions] <--> L[ARC listener]
L --> C[ARC controller]
subgraph VPC["Private VPC / Kubernetes"]
C --> P[Ephemeral runner pods]
P --> E[EKS private endpoint]
P --> R[Private registry]
P --> D[Internal services]
end
F.3 Trust zoning
Use separate scale sets:
arc-general
arc-infra
arc-production
arc-production:
- production repos only;
- no public PRs;
- dedicated Kubernetes namespace;
- restricted service account;
- production network access only as required;
- ephemeral runners;
- audited changes.
Lab G — Debugging a Workflow Systematically
Suppose a production deployment job is not starting.
Use this sequence:
flowchart TD
A[Workflow exists?] --> B[Trigger matched?]
B --> C[Job if true?]
C --> D[needs succeeded?]
D --> E[Environment approved?]
E --> F[Runner available?]
F --> G[Runner labels/group correct?]
G --> H[Job starts]
H --> I[Auth works?]
I --> J[Network works?]
J --> K[Deploy works?]
Do not start by randomly changing IAM and firewall rules. Identify the layer first.
Part IV — Decision Guides and Cheat Sheets
Decision Guide — Which Reuse Mechanism?
| Requirement | YAML anchor | Composite action | Reusable workflow | Workflow template |
|---|---|---|---|---|
| Reuse static YAML | Excellent | No | No | Initial only |
| Reuse steps | Limited | Excellent | Good | Initial only |
| Reuse jobs | No | No | Excellent | Initial only |
| Choose runner | No | No | Yes | Yes |
| Central updates affect callers | No | Yes by version | Yes by version | No |
| Simple repository bootstrap | No | No | Good | Excellent |
| Cross-repository standard | Limited | Yes | Excellent | Excellent |
Decision Guide — Runner Choice
flowchart TD
A[Need a runner] --> B{Private network required?}
B -- No --> C{Special CPU/RAM/GPU/static IP?}
C -- No --> H[Standard GitHub-hosted]
C -- Yes --> L[Larger GitHub-hosted]
B -- Yes --> D{Can supported hosted private networking meet need?}
D -- Yes --> L
D -- No --> E{Kubernetes platform available?}
E -- Yes --> R[ARC ephemeral runners]
E -- No --> S[Ephemeral self-hosted VM runners]
Decision Guide — Cache or Artifact?
flowchart TD
A[Need to persist data?] --> B{Is it required for correctness / promotion?}
B -- Yes --> C[Artifact / package / registry]
B -- No --> D{Is it reusable performance data?}
D -- Yes --> E[Cache]
D -- No --> F[Do not persist]
Decision Guide — pull_request or pull_request_target?
| Need | Event |
|---|---|
| Build/test PR code | pull_request |
| Test fork code safely | pull_request |
| Label/comment using base-repo privileges without running PR code | pull_request_target |
| Privileged follow-up after untrusted CI | often workflow_run |
| Execute fork code with production secret | Do not design this |
Decision Guide — Secret or Variable?
| Value | Use |
|---|---|
| API hostname | variable |
| region | variable |
| feature toggle | variable |
| password | secret |
| private key | secret |
| webhook bearer token | secret |
| cloud access key | preferably replace with OIDC |
| environment name | input/variable |
Cheat Sheet — Minimal Secure PR CI
name: PR CI
on:
pull_request:
permissions:
contents: read
cache-mode: read
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- run: ./ci.sh
Cheat Sheet — Minimal Protected Production Job
jobs:
deploy:
environment: production
permissions:
contents: read
id-token: write
concurrency:
group: production
queue: max
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- run: ./deploy.sh
Cheat Sheet — Data Passing
same command -> shell variable
later step, same job -> GITHUB_ENV
small computed value -> GITHUB_OUTPUT
dependent job -> job output + needs
whole file/directory -> artifact
later workflow/run -> package/artifact/external store
speed optimization -> cache
Cheat Sheet — Troubleshooting
| Symptom | First checks |
|---|---|
| Workflow never starts | event, branch, path, workflow location |
| Job skipped | if, needs, environment |
| Job queued forever | runner labels/group/capacity |
403 GitHub API | permissions, fork policy |
| Cloud auth fails | OIDC permission, audience, subject, role trust |
| Private endpoint fails | runner network/DNS/firewall |
| Cache misses | key/hash/scope/cache-mode |
| Service unavailable | health check/port/hostname/startup |
| Artifact missing | path/name/retention/permissions |
| Works locally, fails in Actions | shell, OS, env, file case, clean checkout |
Part V — Official Reference Links
Use official GitHub documentation as the source of truth for version-sensitive syntax and limits:
- Workflow syntax: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
- Events: https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows
- Workflow commands: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands
- Variables: https://docs.github.com/en/actions/reference/workflows-and-actions/variables
- Expressions: https://docs.github.com/en/actions/reference/workflows-and-actions/expressions
- Contexts: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts
- Deployments and environments: https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments
- Dependency caching: https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching
- Reusing workflow configurations: https://docs.github.com/en/actions/reference/workflows-and-actions/reusing-workflow-configurations
- Metadata syntax: https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax
- GitHub-hosted runners: https://docs.github.com/en/actions/reference/runners/github-hosted-runners
- Larger runners: https://docs.github.com/en/actions/reference/runners/larger-runners
- Secure use: https://docs.github.com/en/actions/reference/security/secure-use
- Secure
pull_request_target: https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target - OIDC: https://docs.github.com/en/actions/reference/security/oidc
- Actions limits: https://docs.github.com/en/actions/reference/limits
- ARC concepts: https://docs.github.com/en/actions/concepts/runners/actions-runner-controller
- Artifact attestations: https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations
Final Production Checklist
Before calling a GitHub Actions platform “production ready”:
Workflow correctness
- [ ] Triggers are narrow and intentional.
- [ ] Job dependencies match the desired DAG.
- [ ] Timeouts exist.
- [ ] Concurrency exists where races are unsafe.
- [ ] Dynamic data uses outputs/artifacts appropriately.
- [ ] CI can succeed without cache.
Security
- [ ]
GITHUB_TOKENis least privilege. - [ ] Untrusted event data is not interpolated into executable shell syntax.
- [ ]
pull_request_targetis not executing untrusted PR code. - [ ] Third-party actions are reviewed and SHA-pinned.
- [ ] Fork PRs do not reach privileged self-hosted runners.
- [ ] Production cloud auth uses OIDC where possible.
- [ ] Production environment is protected.
- [ ] Long-lived secrets are inventoried and rotated.
- [ ] Cache trust boundaries are explicit.
Runners
- [ ] Runner type is appropriate.
- [ ] Self-hosted runners are patched.
- [ ] Sensitive pools are ephemeral.
- [ ] Runner groups restrict repositories.
- [ ] Private network paths are explicit.
- [ ] No unnecessary ambient cloud identity.
- [ ] Capacity/queue metrics exist.
Supply chain
- [ ] Build is reproducible enough for the release model.
- [ ] Release artifacts are immutable.
- [ ] Build once/deploy many is used where practical.
- [ ] Image/package provenance is available where needed.
- [ ] Artifact retention is intentional.
- [ ] Deployment uses exact artifact/image identity.
Governance
- [ ] Required checks enforced.
- [ ] Merge queue workflows support
merge_groupif merge queue is used. - [ ] Actions policy restricts unreviewed code.
- [ ] CODEOWNERS/review policy is configured.
- [ ] Production deployment approvals/protection are configured.
- [ ] Audit and ownership are documented.
Operations
- [ ] Workflow duration/queue/failure metrics are visible.
- [ ] Cost is measured.
- [ ] Common failures have runbooks.
- [ ] Rollback is tested.
- [ ] Central reusable workflows have staged rollout/testing.
- [ ] Action/runner/runtime upgrades have an owner.
Closing Mental Model
If you remember only one architecture, remember this:
flowchart LR
E[Event] --> W[Workflow]
W --> P[Policy + permissions]
P --> J[Jobs]
J --> R[Runners]
R --> S[Steps/actions]
S --> A[Immutable artifacts]
A --> G[Environment gates]
G --> D[Deployment]
D --> V[Verification]
V --> O[Observability + audit]
A production GitHub Actions system is not “some YAML that runs tests.”
It is an event-driven execution platform with:
- identity;
- policy;
- compute;
- data flow;
- supply-chain trust;
- deployment controls;
- observability;
- governance.
Design all eight deliberately.
Appendix A — 2026-Sensitive Features Worth Rechecking
The core GitHub Actions model is stable, but several capabilities in this handbook are newer or especially version-sensitive. Before standardizing them across an enterprise, confirm the current GitHub documentation for your GitHub.com/GHES version.
| Feature | Current handbook behavior |
|---|---|
actions/checkout | Examples use @v6 |
actions/setup-node | Examples use @v7 |
| Cache access | cache-mode: read/write/write-only/none |
| Concurrency queues | queue: max can retain multiple pending runs |
| Step concurrency | background, wait, wait-all, cancel, and parallel |
| Environment without deployment object | environment.deployment: false |
| Same-repository action reference | $/.github/actions/... on supported GitHub.com workflows |
| Reusable workflows | Current reference documents 10 nesting levels and 50 unique called workflows |
| OIDC subject | Newer/opted-in repositories can use immutable owner/repository-ID subject formats |
| Hosted images | Current GitHub-hosted runner catalog includes newer Ubuntu/Windows/macOS/ARM images |
Treat these as capabilities to verify, not assumptions to copy from an older installation.
Appendix B — Complete Source Topic Coverage Audit
This appendix preserves the exact curriculum vocabulary from the supplied topic map so the handbook is searchable and auditable against the requested scope. The explanations, examples, diagrams, and production guidance live in Chapters 1–62; this appendix is a traceability checklist, not a second tutorial.
1. BASIC — GitHub Actions Foundations
1.1 Introduction to GitHub Actions
- [x] GitHub Actions overview
- [x] CI
- [x] Continuous Delivery
- [x] Continuous Deployment
- [x] Automation
- [x] Event-driven automation
- [x] Repository automation
- [x] DevOps automation
- [x] GitHub Actions use cases
- [x] GitHub Actions terminology
- [x] GitHub Actions architecture
- [x] GitHub Actions execution model
- [x] Workflow lifecycle
- [x] Workflow run lifecycle
1.2 Core Components
- [x] Workflows
- [x] Workflow runs
- [x] Events
- [x] Triggers
- [x] Jobs
- [x] Steps
- [x] Actions
- [x] Runners
- [x] Commands
- [x] Scripts
- [x] Artifacts
- [x] Caches
- [x] Variables
- [x] Secrets
- [x] Contexts
- [x] Expressions
- [x] Environments
1.3 Workflow Files
- [x]
.github/workflows - [x]
.yml - [x]
.yaml - [x] Multiple workflows
- [x] Workflow naming
- [x] Workflow file naming
- [x] Workflow version control
- [x] Workflow visibility
- [x] Workflow enablement
- [x] Workflow disablement
1.4 Basic Workflow Structure
- [x]
name - [x]
run-name - [x]
on - [x]
permissions - [x]
env - [x]
defaults - [x]
concurrency - [x]
jobs - [x]
runs-on - [x]
steps - [x]
uses - [x]
run - [x]
with
2. FUNDAMENTALS — Workflow Syntax and Execution
2.1 YAML Fundamentals
- [x] YAML indentation
- [x] YAML mappings
- [x] YAML sequences
- [x] YAML strings
- [x] YAML booleans
- [x] YAML multiline strings
- [x] YAML quoting
- [x] YAML comments
- [x] YAML anchors
- [x] YAML aliases
- [x] YAML reusable structures
- [x] YAML syntax validation
2.2 Workflow-Level Syntax
- [x]
name - [x]
run-name - [x]
on - [x]
permissions - [x]
env - [x]
defaults - [x]
defaults.run - [x]
defaults.run.shell - [x]
defaults.run.working-directory - [x]
concurrency - [x]
jobs - [x]
cache-mode
2.3 Job-Level Syntax
- [x]
jobs.<job_id> - [x] Job IDs
- [x]
name - [x]
permissions - [x]
needs - [x]
if - [x]
runs-on - [x]
environment - [x]
concurrency - [x]
outputs - [x]
env - [x]
defaults - [x]
timeout-minutes - [x]
continue-on-error - [x]
container - [x]
services - [x]
strategy - [x]
uses - [x]
with - [x]
secrets - [x]
cache-mode
2.4 Step-Level Syntax
- [x]
steps - [x] Step IDs
- [x]
id - [x]
name - [x]
if - [x]
uses - [x]
run - [x]
shell - [x]
with - [x]
env - [x]
continue-on-error - [x]
timeout-minutes - [x]
working-directory
3. FUNDAMENTALS — Events and Workflow Triggers
3.1 Trigger Concepts
- [x] GitHub events
- [x] Webhook events
- [x] Activity types
- [x] Manual triggers
- [x] Scheduled triggers
- [x] External triggers
- [x] Multiple triggers
- [x] Trigger filters
- [x] Event payloads
- [x]
github.event - [x]
GITHUB_EVENT_NAME - [x]
GITHUB_EVENT_PATH
3.2 GitHub Actions Events
- [x]
branch_protection_rule - [x]
check_run - [x]
check_suite - [x]
create - [x]
delete - [x]
deployment - [x]
deployment_status - [x]
discussion - [x]
discussion_comment - [x]
fork - [x]
gollum - [x]
image_version - [x]
issue_comment - [x]
issues - [x]
label - [x]
merge_group - [x]
milestone - [x]
page_build - [x]
public - [x]
pull_request - [x]
pull_request_review - [x]
pull_request_review_comment - [x]
pull_request_target - [x]
push - [x]
registry_package - [x]
release - [x]
repository_dispatch - [x]
schedule - [x]
status - [x]
watch - [x]
workflow_call - [x]
workflow_dispatch - [x]
workflow_run
3.3 Trigger Filters
- [x]
types - [x]
branches - [x]
branches-ignore - [x]
tags - [x]
tags-ignore - [x]
paths - [x]
paths-ignore - [x] Glob patterns
- [x] Negative patterns
- [x] Branch filtering
- [x] Tag filtering
- [x] Path filtering
- [x] Activity filtering
- [x] Combined filters
3.4 Manual Workflows
- [x]
workflow_dispatch - [x] Manual workflow execution
- [x] Workflow inputs
- [x] Boolean inputs
- [x] Choice inputs
- [x] String inputs
- [x] Number inputs
- [x] Environment inputs
- [x] Default input values
- [x] Required inputs
3.5 Scheduled Workflows
- [x]
schedule - [x] Cron syntax
- [x] Scheduled workflow limitations
- [x] Default branch execution
3.6 External Workflow Triggers
- [x]
repository_dispatch - [x]
client_payload - [x] REST API triggering
- [x] GitHub CLI triggering
- [x] Cross-repository triggers
4. FUNDAMENTALS — Jobs
4.1 Job Architecture
- [x] Job definition
- [x] Job isolation
- [x] Sequential jobs
- [x] Parallel jobs
- [x] Job dependencies
- [x] Job execution status
- [x] Job conclusions
- [x] Job failure
- [x] Job cancellation
- [x] Job skipping
4.2 Job Dependencies
- [x]
needs - [x] Dependency chains
- [x] Fan-out jobs
- [x] Fan-in jobs
- [x] Dependency outputs
- [x] Conditional dependent jobs
4.3 Job Conditions
- [x] Job-level
if - [x]
success() - [x]
failure() - [x]
cancelled() - [x]
always() - [x] Conditional execution
4.4 Job Outputs
- [x] Job outputs
- [x] Step-to-job outputs
- [x] Job-to-job outputs
- [x]
needscontext - [x] Output expressions
5. FUNDAMENTALS — Steps
5.1 Step Execution
- [x] Action steps
- [x] Shell steps
- [x] Sequential step execution
- [x] Step IDs
- [x] Step names
- [x] Step conditions
- [x] Step outputs
- [x] Step environment
- [x] Step failure
- [x] Step timeout
- [x] Continue-on-error
5.2 Shell Commands
- [x] Bash
- [x]
sh - [x] PowerShell
- [x]
pwsh - [x] Windows Command Prompt
- [x] Python
- [x] Custom shells
- [x] Shell exit codes
- [x] Multiline commands
- [x] Working directories
6. ESSENTIALS — Runners
6.1 Runner Fundamentals
- [x] Runner architecture
- [x] Runner lifecycle
- [x] Runner assignment
- [x] Runner labels
- [x] Runner groups
- [x] Runner environment
- [x] Runner workspace
- [x] Runner temporary directory
- [x] Runner tool cache
6.2 GitHub-Hosted Runners
- [x] Ubuntu runners
- [x] Windows runners
- [x] macOS runners
- [x] ARM runners
- [x] Runner images
- [x] Preinstalled software
- [x] Runner image updates
- [x] Hardware resources
- [x] Runner filesystem
- [x] Hosted runner networking
6.3 Larger Runners
- [x] Larger GitHub-hosted runners
- [x] Runner sizes
- [x] Runner groups
- [x] Static IP addresses
- [x] Private networking
- [x] Autoscaling
- [x] GPU runners
- [x] Larger-runner access control
6.4 Self-Hosted Runners
- [x] Self-hosted runner architecture
- [x] Repository-level runners
- [x] Organization-level runners
- [x] Enterprise-level runners
- [x] Runner installation
- [x] Runner registration
- [x] Runner removal
- [x] Runner labels
- [x] Custom labels
- [x] Runner groups
- [x] Runner services
- [x] Runner updates
- [x] Runner versioning
- [x] Runner networking
- [x] Proxy configuration
- [x] Firewall requirements
- [x] Runner security
- [x] Persistent runners
- [x] Ephemeral runners
- [x] Just-in-time runners
- [x] Runner autoscaling
- [x] Runner routing
- [x] Runner monitoring
- [x] Runner cleanup
6.5 Private Networking
- [x] Private networking for hosted runners
- [x] VNet/VPC connectivity
- [x] Private resources
- [x] Private package registries
- [x] Private cloud endpoints
- [x] Network allowlists
- [x] DNS considerations
7. ESSENTIALS — Variables and Environment Variables
7.1 Environment Variables
- [x] Workflow-level environment variables
- [x] Job-level environment variables
- [x] Step-level environment variables
- [x] Environment variable precedence
- [x] Default environment variables
- [x] Custom environment variables
7.2 Configuration Variables
- [x] Repository variables
- [x] Organization variables
- [x] Environment variables
- [x] Variable scopes
- [x] Variable precedence
- [x] Variable naming
- [x] Variable limits
- [x]
varscontext
7.3 GitHub Default Variables
- [x]
CI - [x]
GITHUB_ACTION - [x]
GITHUB_ACTION_PATH - [x]
GITHUB_ACTION_REPOSITORY - [x]
GITHUB_ACTIONS - [x]
GITHUB_ACTOR - [x]
GITHUB_ACTOR_ID - [x]
GITHUB_API_URL - [x]
GITHUB_BASE_REF - [x]
GITHUB_ENV - [x]
GITHUB_EVENT_NAME - [x]
GITHUB_EVENT_PATH - [x]
GITHUB_GRAPHQL_URL - [x]
GITHUB_HEAD_REF - [x]
GITHUB_JOB - [x]
GITHUB_OUTPUT - [x]
GITHUB_PATH - [x]
GITHUB_REF - [x]
GITHUB_REF_NAME - [x]
GITHUB_REF_PROTECTED - [x]
GITHUB_REF_TYPE - [x]
GITHUB_REPOSITORY - [x]
GITHUB_REPOSITORY_ID - [x]
GITHUB_REPOSITORY_OWNER - [x]
GITHUB_RETENTION_DAYS - [x]
GITHUB_RUN_ATTEMPT - [x]
GITHUB_RUN_ID - [x]
GITHUB_RUN_NUMBER - [x]
GITHUB_SERVER_URL - [x]
GITHUB_SHA - [x]
GITHUB_STEP_SUMMARY - [x]
GITHUB_TRIGGERING_ACTOR - [x]
GITHUB_WORKFLOW - [x]
GITHUB_WORKFLOW_REF - [x]
GITHUB_WORKFLOW_SHA - [x]
GITHUB_WORKSPACE - [x]
RUNNER_ARCH - [x]
RUNNER_ENVIRONMENT - [x]
RUNNER_NAME - [x]
RUNNER_OS - [x]
RUNNER_TEMP - [x]
RUNNER_TOOL_CACHE
8. ESSENTIALS — Contexts
- [x]
github - [x]
env - [x]
vars - [x]
job - [x]
jobs - [x]
steps - [x]
runner - [x]
secrets - [x]
strategy - [x]
matrix - [x]
needs - [x]
inputs - [x] Context availability
- [x] Context property access
- [x] Property dereference syntax
- [x] Index syntax
- [x] Context security
- [x] Context debugging
- [x] Context dumping
- [x] Context availability by workflow key
9. ESSENTIALS — Expressions
9.1 Expression Syntax
- [x]
${{ }} - [x] Literals
- [x] Booleans
- [x] Null
- [x] Numbers
- [x] Strings
- [x] Operators
- [x] Operator precedence
- [x] Property access
- [x] Object filters
- [x] Loose equality
- [x] Type coercion
9.2 Expression Operators
- [x]
! - [x]
> - [x]
>= - [x]
< - [x]
<= - [x]
== - [x]
!= - [x]
&& - [x]
||
9.3 Expression Functions
- [x]
contains() - [x]
startsWith() - [x]
endsWith() - [x]
format() - [x]
join() - [x]
toJSON() - [x]
fromJSON() - [x]
hashFiles()
9.4 Status Functions
- [x]
success() - [x]
always() - [x]
cancelled() - [x]
failure()
10. ESSENTIALS — Secrets
- [x] Repository secrets
- [x] Organization secrets
- [x] Environment secrets
- [x] Secret scopes
- [x] Secret precedence
- [x] Secret naming
- [x] Secret limits
- [x] Secret access policies
- [x] Secret masking
- [x] Secret redaction
- [x] Secret inheritance
- [x] Secrets in reusable workflows
- [x] Secrets in forked pull requests
- [x] Secrets with Dependabot
- [x] Secrets with command-line tools
- [x] Large secrets
- [x] Structured secrets
- [x] Secret rotation
- [x] Least-privilege secrets
- [x]
secretscontext - [x]
GITHUB_TOKEN
11. ESSENTIALS — GITHUB_TOKEN
- [x] Automatic token generation
- [x] Token lifecycle
- [x] Token scope
- [x] Token expiration
- [x] Repository access
- [x]
permissions - [x] Workflow-level permissions
- [x] Job-level permissions
- [x] Read permissions
- [x] Write permissions
- [x]
contents - [x]
actions - [x]
attestations - [x]
checks - [x]
deployments - [x]
discussions - [x]
id-token - [x]
issues - [x]
models - [x]
packages - [x]
pages - [x]
pull-requests - [x]
security-events - [x]
statuses - [x] Least privilege
- [x] Token-triggered workflow behavior
- [x] GitHub API authentication
- [x] GitHub CLI authentication
12. ESSENTIALS — Workflow Commands
- [x] Workflow command syntax
- [x] Environment files
- [x]
GITHUB_ENV - [x]
GITHUB_OUTPUT - [x]
GITHUB_PATH - [x]
GITHUB_STEP_SUMMARY - [x] Setting environment variables
- [x] Setting outputs
- [x] Adding system paths
- [x] Job summaries
- [x] Error annotations
- [x] Warning annotations
- [x] Notice annotations
- [x] Debug messages
- [x] Log grouping
- [x]
group - [x]
endgroup - [x] Secret masking
- [x]
add-mask - [x] Stopping workflow commands
- [x] Action state
- [x]
GITHUB_STATE
13. ESSENTIALS — Matrix Builds
- [x] Matrix strategy
- [x]
strategy.matrix - [x] Multi-dimensional matrices
- [x] Operating-system matrices
- [x] Runtime-version matrices
- [x] Include
- [x] Exclude
- [x] Matrix expansion
- [x] Dynamic matrices
- [x] Matrix from JSON
- [x] Matrix outputs
- [x] Matrix job dependencies
- [x]
max-parallel - [x]
fail-fast - [x] Matrix
continue-on-error - [x] Experimental matrix jobs
- [x] Matrix limits
14. ESSENTIALS — Caching
- [x] Dependency caching
- [x]
actions/cache - [x] Automatic dependency caching
- [x] Cache keys
- [x] Restore keys
- [x] Cache matching
- [x] Cache scope
- [x] Cache version
- [x] Cache hits
- [x] Cache misses
- [x] Cross-OS cache
- [x] Cache eviction
- [x] Cache retention
- [x] Cache limits
- [x] Cache security
- [x] Cache poisoning
- [x] Cache access restrictions
- [x]
cache-mode - [x]
read - [x]
write - [x]
write-only - [x]
none - [x] Trigger-sensitive cache permissions
- [x] Package-manager caches
- [x] npm caching
- [x] Yarn caching
- [x] pnpm caching
- [x] Maven caching
- [x] Gradle caching
- [x] pip caching
- [x] Bundler caching
- [x] NuGet caching
15. ESSENTIALS — Artifacts
- [x] Workflow artifacts
- [x] Artifact upload
- [x] Artifact download
- [x] Artifact naming
- [x] Artifact paths
- [x] Artifact compression
- [x] Artifact retention
- [x] Artifact expiration
- [x] Artifact IDs
- [x] Artifact URLs
- [x] Artifact digests
- [x] Artifact sharing between jobs
- [x] Artifact sharing between workflow runs
- [x] Artifact permissions
- [x] Artifact deletion
- [x] Artifact API
- [x] Cache vs artifacts
- [x] Build artifacts
- [x] Test artifacts
- [x] Log artifacts
- [x] Deployment artifacts
- [x] Artifact attestations
16. ESSENTIALS — Concurrency
- [x] Workflow concurrency
- [x] Job concurrency
- [x] Concurrency groups
- [x] Dynamic concurrency groups
- [x]
cancel-in-progress - [x] Pending runs
- [x] Running runs
- [x] Deployment serialization
- [x] Branch-specific concurrency
- [x] Environment-specific concurrency
- [x] Workflow cancellation
17. ESSENTIALS — Containers
17.1 Container Jobs
- [x]
container - [x] Container images
- [x] Container credentials
- [x] Container environment variables
- [x] Container ports
- [x] Container volumes
- [x] Container options
- [x] Docker Hub
- [x] GitHub Container Registry
- [x] Private container registries
17.2 Service Containers
- [x]
services - [x] Database services
- [x] Redis
- [x] PostgreSQL
- [x] MySQL
- [x] MongoDB
- [x] Service networking
- [x] Port mappings
- [x] Health checks
- [x] Service credentials
- [x] Container-to-container communication
18. ESSENTIALS — Using Actions
- [x] GitHub Marketplace
- [x] Official GitHub actions
- [x] Community actions
- [x] Public actions
- [x] Private actions
- [x] Local repository actions
- [x] Referencing actions
- [x] Action inputs
- [x] Action outputs
- [x] Action versions
- [x] Tags
- [x] Branch references
- [x] Commit SHA pinning
- [x] Semantic versioning
- [x] Action updates
- [x] Action dependency management
- [x] Action trust
- [x] Third-party action review
- [x] Deprecated actions
- [x] Node runtime migrations
19. ESSENTIALS — Reusable Automation
19.1 Reusable Workflows
- [x]
workflow_call - [x] Called workflows
- [x] Caller workflows
- [x] Reusable workflow inputs
- [x] Reusable workflow secrets
- [x]
secrets: inherit - [x] Reusable workflow outputs
- [x] Job outputs
- [x] Nested reusable workflows
- [x] Reusable workflow permissions
- [x] Reusable workflow environments
- [x] Reusable workflow matrices
- [x] Cross-repository reusable workflows
- [x] Private reusable workflows
- [x] Workflow versioning
- [x] Workflow pinning
- [x] Reusable workflow access rules
- [x] Reusable workflow limitations
19.2 Workflow Templates
- [x] Organization workflow templates
- [x] Starter workflows
- [x] Workflow template repositories
- [x] Template metadata
- [x] Organization CI standards
19.3 YAML Reuse
- [x] YAML anchors
- [x] YAML aliases
20. ESSENTIALS — CI Pipelines
- [x] Checkout
- [x] Dependency installation
- [x] Dependency caching
- [x] Compilation
- [x] Linting
- [x] Formatting
- [x] Unit testing
- [x] Integration testing
- [x] Functional testing
- [x] End-to-end testing
- [x] Security testing
- [x] Code coverage
- [x] Test reports
- [x] Build packaging
- [x] Artifact publishing
- [x] Pipeline status checks
- [x] Pull-request validation
- [x] Merge validation
- [x] Branch builds
- [x] Tag builds
- [x] Release builds
21. ESSENTIALS — CD Pipelines
- [x] Continuous delivery
- [x] Continuous deployment
- [x] Deployment jobs
- [x] Deployment workflows
- [x] Deployment environments
- [x] Development deployments
- [x] Staging deployments
- [x] UAT deployments
- [x] Production deployments
- [x] Manual approvals
- [x] Deployment gates
- [x] Deployment promotion
- [x] Deployment rollback
- [x] Deployment verification
- [x] Post-deployment testing
- [x] Deployment status
- [x] Deployment history
22. ESSENTIALS — Environments and Deployments
- [x] GitHub environments
- [x] Environment names
- [x] Environment URLs
- [x] Environment secrets
- [x] Environment variables
- [x] Required reviewers
- [x] Deployment protection rules
- [x] Custom deployment protection rules
- [x] Wait timers
- [x] Branch restrictions
- [x] Tag restrictions
- [x] Environment approvals
- [x] Environment deployments
- [x] Environment concurrency
- [x] Deployment history
- [x] Deployment API
23. ADVANCED — Custom Actions
23.1 Custom Action Fundamentals
- [x] Custom action architecture
- [x] Action repositories
- [x] Action metadata
- [x]
action.yml - [x]
action.yaml - [x] Inputs
- [x] Outputs
- [x] Environment variables
- [x] Action branding
- [x] Action versioning
- [x] Action publishing
23.2 JavaScript Actions
- [x] JavaScript actions
- [x] Node.js runtime
- [x]
@actions/core - [x]
@actions/github - [x]
@actions/exec - [x]
@actions/io - [x]
@actions/tool-cache - [x] GitHub REST API
- [x] Octokit
- [x] Action inputs
- [x] Action outputs
- [x] Action failures
- [x] Action logging
- [x] Pre-actions
- [x] Post-actions
- [x] State management
- [x] Packaging dependencies
- [x]
ncc - [x] JavaScript action releases
23.3 Docker Container Actions
- [x] Docker actions
- [x]
Dockerfile - [x] Docker action metadata
- [x] Docker action inputs
- [x] Docker action arguments
- [x] Docker entrypoints
- [x] Container environment
- [x] Container filesystem
- [x] Container exit codes
- [x] Docker action publishing
23.4 Composite Actions
- [x] Composite actions
- [x] Composite steps
- [x] Composite inputs
- [x] Composite outputs
- [x] Composite environment handling
- [x] Nested actions
- [x] Composite action limitations
23.5 Action Metadata Syntax
- [x]
name - [x]
author - [x]
description - [x]
inputs - [x]
outputs - [x]
runs - [x]
using - [x]
main - [x]
pre - [x]
post - [x]
pre-if - [x]
post-if - [x]
image - [x]
entrypoint - [x]
args - [x] Composite
steps - [x]
branding
24. ADVANCED — Workflow Data Flow
- [x] Step outputs
- [x] Job outputs
- [x] Workflow outputs
- [x] Reusable workflow outputs
- [x] Environment files
- [x] Context-based data passing
- [x] JSON serialization
- [x]
toJSON - [x]
fromJSON - [x] Artifact-based data transfer
- [x] Output-based data transfer
- [x] Matrix-generated data
- [x] Dynamic workflow data
- [x] State between pre/main/post actions
25. ADVANCED — Conditional Workflows
- [x] Conditional jobs
- [x] Conditional steps
- [x] Event-based conditions
- [x] Branch conditions
- [x] Tag conditions
- [x] Actor conditions
- [x] Repository conditions
- [x] Environment conditions
- [x] Matrix conditions
- [x] Changed-path conditions
- [x] Pull-request conditions
- [x] Fork conditions
- [x] Failure handling
- [x] Cleanup jobs
- [x] Always-run jobs
26. ADVANCED — Error Handling
- [x] Exit codes
- [x] Step failures
- [x] Job failures
- [x] Workflow failures
- [x]
continue-on-error - [x] Matrix experimental failures
- [x]
fail-fast - [x] Retry patterns
- [x] Conditional retry
- [x] Cleanup on failure
- [x] Rollback on failure
- [x]
success() - [x]
failure() - [x]
cancelled() - [x]
always()
27. ADVANCED — Troubleshooting and Debugging
- [x] Workflow syntax errors
- [x] YAML errors
- [x] Trigger debugging
- [x] Context debugging
- [x] Expression debugging
- [x] Runner debugging
- [x] Action debugging
- [x] Container debugging
- [x] Service-container debugging
- [x] Permissions errors
- [x] Authentication errors
- [x] Secret errors
- [x] Environment errors
- [x] Cache errors
- [x] Artifact errors
- [x] Matrix errors
- [x] Reusable workflow errors
- [x] Deployment errors
- [x] Timeout troubleshooting
- [x] Network troubleshooting
- [x] DNS troubleshooting
- [x] Workflow logs
- [x] Job logs
- [x] Step logs
- [x] Log search
- [x] Log download
- [x] Debug logging
- [x] Runner diagnostic logging
- [x] Re-running workflows
- [x] Re-running failed jobs
- [x] Re-running individual jobs
- [x] Workflow run attempts
- [x] Annotations
- [x] Job summaries
28. ADVANCED — Security Hardening
28.1 Workflow Security
- [x] Least privilege
- [x] Workflow permissions
- [x] Job permissions
- [x] Untrusted input
- [x] Script injection
- [x] Command injection
- [x] Expression injection
- [x] Shell injection
- [x] Pull-request security
- [x] Fork security
- [x] Public repository workflow security
28.2 Third-Party Action Security
- [x] Action provenance
- [x] Commit SHA pinning
- [x] Mutable tags
- [x] Action review
- [x] Dependency review
- [x] Supply-chain attacks
- [x] Compromised actions
- [x] Action allowlists
28.3 pull_request Security
- [x] Fork pull requests
- [x] Read-only tokens
- [x] Secret restrictions
- [x] Workflow approval
- [x] Untrusted code execution
28.4 pull_request_target Security
- [x]
pull_request_target - [x] Privileged workflow context
- [x] Untrusted checkout risks
- [x] Secret exposure
- [x] Token exposure
- [x] Cache poisoning
- [x] Secure
pull_request_targetpatterns
28.5 Runner Security
- [x] Hosted runner isolation
- [x] Self-hosted runner risks
- [x] Persistent runner risks
- [x] Ephemeral runners
- [x] Runner compromise
- [x] Runner credential cleanup
- [x] Network isolation
- [x] Runner trust boundaries
- [x] Public repository runner risks
29. ADVANCED — OpenID Connect
- [x] OIDC
- [x] Federated authentication
- [x] Short-lived credentials
- [x] ID tokens
- [x]
id-token: write - [x] OIDC token claims
- [x] OIDC subject claims
- [x] Custom subject claims
- [x] Audience claims
- [x] Repository claims
- [x] Branch claims
- [x] Tag claims
- [x] Environment claims
- [x] Cloud trust policies
- [x] AWS OIDC
- [x] Azure OIDC
- [x] Google Cloud OIDC
- [x] HashiCorp Vault OIDC
- [x] Cloud role assumption
- [x] Credential-less cloud deployments
- [x] OIDC security
30. ADVANCED — Artifact Attestations
- [x] Artifact provenance
- [x] Artifact attestations
- [x] Build provenance
- [x] Attestation generation
- [x] Attestation verification
- [x] Signed provenance
- [x] Software supply-chain integrity
- [x] Container image attestations
- [x] Binary attestations
- [x] Package attestations
- [x] Kubernetes attestation enforcement
- [x] Kubernetes admission controller
31. ADVANCED — Docker and Container CI/CD
- [x] Docker builds
- [x] Docker Buildx
- [x] BuildKit
- [x] Multi-platform builds
- [x] Docker layer caching
- [x] Registry authentication
- [x] GitHub Container Registry
- [x] Container tagging
- [x] Container metadata
- [x] Image publishing
- [x] Image signing
- [x] Image attestations
- [x] Image scanning
- [x] Multi-architecture images
- [x] Release images
32. ADVANCED — Package Publishing
- [x] GitHub Packages
- [x] npm packages
- [x] Maven packages
- [x] Gradle packages
- [x] NuGet packages
- [x] RubyGems
- [x] Container packages
- [x] Package authentication
- [x] Package permissions
- [x] Package publishing
- [x] Package installation
- [x] Package versioning
- [x] Package provenance
33. ADVANCED — Releases
- [x] Release workflows
- [x] Git tags
- [x] Release events
- [x] GitHub Releases
- [x] Release assets
- [x] Release notes
- [x] Automated releases
- [x] Semantic versioning
- [x] Pre-releases
- [x] Draft releases
- [x] Release artifact publishing
- [x] Release promotion
34. ADVANCED — Cloud Deployments
34.1 AWS
- [x] AWS authentication
- [x] AWS OIDC
- [x] IAM roles
- [x] STS AssumeRole
- [x] S3 deployments
- [x] EC2 deployments
- [x] ECS deployments
- [x] ECR publishing
- [x] EKS deployments
- [x] Lambda deployments
- [x] CloudFormation deployments
- [x] Terraform deployments
34.2 Azure
- [x] Azure authentication
- [x] Azure OIDC
- [x] Azure Web Apps
- [x] Azure Container Apps
- [x] AKS
- [x] Azure Functions
34.3 Google Cloud
- [x] Google Cloud authentication
- [x] Workload Identity Federation
- [x] GKE
- [x] Cloud Run
- [x] Artifact Registry
35. ADVANCED — Kubernetes CI/CD
- [x] Kubernetes authentication
- [x] kubeconfig management
- [x] OIDC authentication
- [x] Kubernetes manifests
- [x] Helm
- [x] Kustomize
- [x] kubectl
- [x] Namespace deployments
- [x] Deployment rollout
- [x] Deployment verification
- [x] Rollback
- [x] EKS
- [x] AKS
- [x] GKE
- [x] Container registry integration
- [x] GitOps integration
36. ADVANCED — Infrastructure as Code
- [x] Terraform workflows
- [x] Terraform formatting
- [x] Terraform validation
- [x] Terraform plan
- [x] Terraform apply
- [x] Terraform destroy
- [x] Terraform state
- [x] Terraform workspaces
- [x] Terraform Cloud
- [x] OpenTofu
- [x] CloudFormation
- [x] CDK
- [x] Pulumi
- [x] Infrastructure approvals
- [x] Plan artifacts
- [x] IaC security scanning
- [x] Drift detection
37. ADVANCED — Self-Hosted Runner Architecture
- [x] Runner fleet architecture
- [x] Dedicated runners
- [x] Shared runners
- [x] Ephemeral runners
- [x] Autoscaling runners
- [x] JIT runners
- [x] Runner groups
- [x] Custom runner labels
- [x] Repository runner isolation
- [x] Organization runner isolation
- [x] Enterprise runner isolation
- [x] Runner images
- [x] Golden runner images
- [x] Runner patching
- [x] Runner upgrades
- [x] Runner network access
- [x] Private subnet runners
- [x] Proxy runners
- [x] Runner observability
- [x] Runner credential management
- [x] Runner lifecycle automation
38. ADVANCED — Actions Runner Controller
- [x] Actions Runner Controller
- [x] ARC architecture
- [x] Kubernetes-based runners
- [x] Runner scale sets
- [x] Autoscaling runner scale sets
- [x] Scale-set controller
- [x] Listener
- [x] Runner pods
- [x] Ephemeral runner pods
- [x] ARC Helm charts
- [x] GitHub App authentication
- [x] PAT authentication
- [x] Kubernetes RBAC
- [x] Runner namespaces
- [x] Custom runner images
- [x] Docker-in-Docker
- [x] Kubernetes container mode
- [x] Runner storage
- [x] Runner networking
- [x] ARC autoscaling
- [x] Minimum runners
- [x] Maximum runners
- [x] Runner groups
- [x] ARC upgrades
- [x] ARC monitoring
- [x] ARC troubleshooting
- [x] ARC security
39. ADVANCED — Workflow Performance
- [x] Workflow runtime optimization
- [x] Parallel jobs
- [x] Matrix parallelism
- [x] Dependency caching
- [x] Build caching
- [x] Docker caching
- [x] Artifact optimization
- [x] Checkout optimization
- [x] Shallow clones
- [x] Sparse checkout
- [x] Dependency reuse
- [x] Selective execution
- [x] Path filtering
- [x] Job dependency optimization
- [x] Runner selection
- [x] Larger runners
- [x] Self-hosted runner performance
40. ADVANCED — Cost Optimization
- [x] GitHub Actions minutes
- [x] Storage usage
- [x] Artifact storage
- [x] Cache storage
- [x] Hosted runner billing
- [x] Larger runner billing
- [x] Private repository usage
- [x] Public repository usage
- [x] Workflow runtime reduction
- [x] Matrix cost control
- [x] Retention optimization
- [x] Self-hosted runner economics
- [x] Usage monitoring
- [x] Billing reports
41. ADVANCED — Monorepo Workflows
- [x] Monorepo CI
- [x] Path filters
- [x] Selective builds
- [x] Selective tests
- [x] Component matrices
- [x] Dynamic matrices
- [x] Changed component detection
- [x] Independent deployments
- [x] Shared workflows
- [x] Shared actions
- [x] Dependency graph workflows
- [x] Fan-out builds
- [x] Fan-in validation
- [x] Monorepo caching
- [x] Monorepo concurrency
42. ADVANCED — Multi-Repository Workflows
- [x] Cross-repository workflows
- [x]
repository_dispatch - [x] Reusable workflows across repositories
- [x] Private reusable workflows
- [x] Repository access policies
- [x] Cross-repository deployment
- [x] Central CI repositories
- [x] Central workflow repositories
- [x] Organization automation repositories
- [x] GitHub App authentication
- [x] Fine-grained PAT authentication
- [x] Cross-repository artifacts
- [x] Cross-repository orchestration
43. ADVANCED — Workflow Chaining
- [x]
workflow_run - [x] Workflow dependencies
- [x] Upstream workflows
- [x] Downstream workflows
- [x] Multi-stage workflow chains
- [x] Artifact passing between workflow runs
- [x] Privilege boundaries
- [x] Workflow chain security
- [x] Workflow chain limits
44. ADVANCED — GitHub API Integration
- [x] REST API
- [x] GraphQL API
- [x] GitHub CLI
- [x]
gh - [x]
gh api - [x] Octokit
- [x]
actions/github-script - [x] Issues automation
- [x] Pull-request automation
- [x] Release automation
- [x] Repository automation
- [x] Deployment API
- [x] Checks API
- [x] Status API
- [x] Actions API
- [x] Workflow API
- [x] Artifact API
- [x] Cache API
- [x] Runner API
45. ADVANCED — Repository Governance
- [x] Branch protection
- [x] Rulesets
- [x] Required status checks
- [x] Required workflows
- [x] Merge queues
- [x]
merge_group - [x] CODEOWNERS interaction
- [x] Pull-request approval
- [x] Workflow approval
- [x] Deployment approval
- [x] Environment protection
- [x] Repository Actions settings
- [x] Workflow permissions
46. ADVANCED — Organization Governance
- [x] Organization Actions policies
- [x] Allowed actions
- [x] Selected actions
- [x] Marketplace action restrictions
- [x] SHA pinning policies
- [x] Reusable workflow governance
- [x] Runner groups
- [x] Organization secrets
- [x] Organization variables
- [x] Secret repository access
- [x] Runner repository access
- [x] Private action sharing
- [x] Workflow retention
- [x] Workflow permissions
- [x] Fork workflow policies
47. ADVANCED — Enterprise Governance
- [x] Enterprise Actions policies
- [x] Enterprise runner groups
- [x] Enterprise secrets
- [x] Enterprise workflow policies
- [x] Enterprise action restrictions
- [x] Enterprise runner management
- [x] Organization policy inheritance
- [x] Actions usage policies
- [x] Workflow execution policies
- [x] Audit logs
- [x] Enterprise security controls
- [x] Enterprise billing
- [x] Enterprise usage monitoring
48. ADVANCED — Workflow Execution Policies
- [x] Workflow execution protections
- [x] Workflow execution allowlists
- [x] Actor restrictions
- [x] Event restrictions
- [x] Repository policies
- [x] Organization policies
- [x] Enterprise policies
- [x] Policy inheritance
- [x] Policy evaluation
- [x] Workflow execution governance
49. ADVANCED — Metrics and Observability
- [x] GitHub Actions metrics
- [x] Workflow metrics
- [x] Job metrics
- [x] Workflow duration
- [x] Queue duration
- [x] Job duration
- [x] Workflow success rate
- [x] Workflow failure rate
- [x] Runner utilization
- [x] Runner availability
- [x] Usage metrics
- [x] Performance analysis
- [x] Cost analysis
- [x] Audit logs
- [x] Workflow notifications
50. ADVANCED — Workflow Notifications
- [x] Workflow run notifications
- [x] Email notifications
- [x] Web notifications
- [x] Failed workflow notifications
- [x] GitHub notifications
- [x] Slack integrations
- [x] Microsoft Teams integrations
- [x] Custom webhook notifications
- [x] Deployment notifications
51. ADVANCED — Workflow Cancellation
- [x] Manual cancellation
- [x] Automatic cancellation
- [x] Concurrency cancellation
- [x]
cancel-in-progress - [x] Job cancellation
- [x] Step cancellation
- [x] Cancellation signals
- [x] Cancellation conditions
- [x] Cleanup after cancellation
- [x]
cancelled() - [x] Workflow cancellation lifecycle
52. ADVANCED — Pull Request CI Patterns
- [x] PR validation
- [x] Fork PR validation
- [x] Draft PR workflows
- [x] PR activity types
- [x] Required checks
- [x] Merge queues
- [x] Merge-group checks
- [x] Changed-file workflows
- [x] PR labeling automation
- [x] PR commenting
- [x] PR test reports
- [x] PR preview environments
- [x] PR security scanning
- [x] PR cleanup workflows
53. ADVANCED — Branch and Release Strategies
- [x] Feature branches
- [x] Trunk-based development
- [x] GitFlow
- [x] Release branches
- [x] Environment branches
- [x] Branch-based deployments
- [x] Tag-based deployments
- [x] Semantic version tags
- [x] Hotfix workflows
- [x] Promotion workflows
54. ADVANCED — Deployment Strategies
- [x] Rolling deployment
- [x] Blue/green deployment
- [x] Canary deployment
- [x] Progressive delivery
- [x] Feature-flag deployment
- [x] Environment promotion
- [x] Immutable deployment
- [x] Manual deployment
- [x] Automated deployment
- [x] Rollback workflows
- [x] Deployment verification
- [x] Smoke testing
55. ADVANCED — Testing GitHub Actions
- [x] Workflow syntax validation
- [x] Workflow linting
- [x]
actionlint - [x] YAML validation
- [x] Custom action unit testing
- [x] JavaScript action testing
- [x] Docker action testing
- [x] Composite action testing
- [x] Reusable workflow testing
- [x] Integration testing
- [x] Staging workflow testing
- [x] Local workflow emulation
- [x]
act - [x] Mock contexts
- [x] Mock events
- [x] Test repositories
56. ADVANCED — Workflow Maintenance
- [x] Action version upgrades
- [x] Deprecated action versions
- [x] Runner image changes
- [x] Node runtime changes
- [x] Dependency updates
- [x] Dependabot for Actions
- [x] Workflow refactoring
- [x] Workflow modularization
- [x] Reusable workflow adoption
- [x] Action pinning maintenance
- [x] Secret rotation
- [x] Runner upgrades
- [x] Workflow documentation
57. ADVANCED — GitHub Actions Limits
- [x] Workflow execution limits
- [x] Job execution limits
- [x] Matrix limits
- [x] Concurrency limits
- [x] Workflow queue limits
- [x] Runner limits
- [x] Self-hosted runner limits
- [x] Artifact limits
- [x] Cache limits
- [x] Secret limits
- [x] Variable limits
- [x] Input limits
- [x] Output limits
- [x] Reusable workflow limits
- [x] Workflow nesting limits
- [x] Workflow retention limits
- [x] API rate limits
58. ADVANCED — GitHub Actions Importer
- [x] GitHub Actions Importer
- [x] CI migration
- [x] Migration planning
- [x] Workflow auditing
- [x] Workflow conversion
- [x] Supplemental arguments
- [x] Importer configuration
- [x] Custom transformers
- [x] Migration validation
- [x] Migration from Jenkins
- [x] Migration from GitLab CI/CD
- [x] Migration from CircleCI
- [x] Migration from Azure DevOps
- [x] Migration from Travis CI
59. ADVANCED — Common CI/CD Design Patterns
- [x] Build-test-deploy
- [x] Build once, deploy many
- [x] Fan-out/fan-in
- [x] Matrix pipelines
- [x] Reusable pipeline architecture
- [x] Centralized workflow architecture
- [x] Environment promotion
- [x] Artifact promotion
- [x] Conditional deployment
- [x] Manual approval gates
- [x] Automated rollback
- [x] Scheduled automation
- [x] Event-driven automation
- [x] Cross-repository automation
- [x] Dynamic pipeline generation
60. ADVANCED — GitHub Actions Anti-Patterns
- [x] Duplicate workflow logic
- [x] Overprivileged
GITHUB_TOKEN - [x] Long-lived cloud credentials
- [x] Unpinned third-party actions
- [x] Mutable action references
- [x] Secrets in workflow files
- [x] Secrets in logs
- [x] Untrusted input in shell commands
- [x] Unsafe
pull_request_target - [x] Persistent public self-hosted runners
- [x] Excessive matrix jobs
- [x] Cache misuse
- [x] Cache poisoning
- [x] Artifact misuse
- [x] Hard-coded environment values
- [x] Excessive workflow chaining
- [x] Monolithic workflows
- [x] Missing timeouts
- [x] Missing concurrency controls
- [x] Missing deployment protections
61. ADVANCED — Production Best Practices
- [x] Least-privilege permissions
- [x] OIDC cloud authentication
- [x] SHA-pinned actions
- [x] Reusable workflows
- [x] Composite actions
- [x] Environment protection
- [x] Required reviewers
- [x] Concurrency controls
- [x] Job timeouts
- [x] Dependency caching
- [x] Artifact retention
- [x] Ephemeral runners
- [x] Runner isolation
- [x] Secret rotation
- [x] Branch protection
- [x] Rulesets
- [x] Required checks
- [x] Workflow monitoring
- [x] Workflow metrics
- [x] CI/CD auditability
- [x] Supply-chain security
- [x] Artifact provenance
- [x] Workflow versioning
- [x] Deployment traceability
- [x] Rollback readiness
62. ADVANCED — Reference and Administration
- [x] Workflow syntax reference
- [x] Events reference
- [x] Workflow commands reference
- [x] Variables reference
- [x] Expressions reference
- [x] Contexts reference
- [x] Deployments reference
- [x] Environments reference
- [x] Dependency caching reference
- [x] Reusable configurations reference
- [x] Action metadata reference
- [x] Workflow cancellation reference
- [x] Dockerfile support reference
- [x] GitHub-hosted runners reference
- [x] Larger runners reference
- [x] Self-hosted runners reference
- [x] Security reference
- [x] Secrets reference
- [x] OIDC reference
- [x] GitHub Actions limits
- [x] GitHub Actions Importer reference
- [x] Billing and usage
- [x] Actions policies
- [x] Actions metrics
- [x] Runner administration
- [x] Workflow administration
- [x] Repository administration
- [x] Organization administration
- [x] Enterprise administration