GitHub Actions runs code in response to things that happen in a repository.
That is the whole primitive. Someone pushes a commit, opens a pull request, publishes a release or waits for a scheduled time; GitHub starts a workflow, which runs jobs on runners, and each job executes steps that are either shell commands or reusable actions.
Everything in this pillar — continuous integration, container builds, cloud deployments, supply-chain attestation — is that primitive applied with increasing care.
Start with GitHub Actions FundamentalsWhere this sits
Section titled “Where this sits”The previous three pillars built up to a repository where changes arrive as pull requests and merge under policy. This pillar is what happens around those events automatically.
A vertical chain: a Git repository produces a GitHub event, which starts a workflow, which contains jobs, which run on a runner, executing steps and actions that build, test and scan, producing an artifact, which is deployed to an environment.
The dashed stages matter. Most workflows do not deploy anything. A repository whose Actions usage is entirely “run the tests on every pull request” is using the platform correctly and completely. CI is the common case; deployment is a specialisation.
The vocabulary, once
Section titled “The vocabulary, once”Six words carry most of the weight, and they are used loosely everywhere else on the internet.
| Term | What it is |
|---|---|
| Workflow | A YAML file in .github/workflows/ describing automation |
| Event | The thing that happened — a push, a pull request, a schedule firing |
| Job | A unit of work that runs on one runner; jobs run in parallel by default |
| Runner | The machine executing a job |
| Step | One command or one action inside a job |
| Action | A reusable component a step can call with uses: |
The distinction that trips people up most:
GitHub Actions is the platform. An action is a reusable component you call from a step.
A workflow with no uses: lines contains no actions at all, and it is still GitHub Actions. That
ambiguity is baked into the product’s name and it is worth being deliberate about — this pillar
always says “an action” or “the platform” rather than relying on context.
A minimal workflow
Section titled “A minimal workflow”Concretely, this is the whole shape:
name: CI
on: pull_request: branches: [main]
permissions: contents: read
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - run: ./run-tests.shSeven meaningful lines. An event, a permission grant, one job, one runner, two steps — the first an action that clones the repository, the second a shell command.
Everything else in this pillar is that structure with more of it: more jobs, conditions between them, values passed along, credentials obtained safely, and outputs preserved.
CI, delivery, deployment
Section titled “CI, delivery, deployment”Three terms used interchangeably in conversation and distinctly here.
Continuous integration is validating changes as they are integrated: build, lint, test, scan. Its output is a verdict — this change is or is not safe to merge.
Continuous delivery is ensuring every validated change could be released. It produces a deployable artefact and proves it deploys, without necessarily releasing it.
Continuous deployment is releasing every validated change automatically, with no human gate.
A successful build should not automatically imply an uncontrolled production deployment.
Which of these you want is an organisational decision, not a technical one. A team deploying twenty times a day and a team deploying quarterly can both be doing continuous integration correctly. The Continuous Delivery cluster treats the gate between them as the design decision it is.
The lifecycle this pillar teaches
Section titled “The lifecycle this pillar teaches”A sequence: a commit becomes a pull request, which triggers CI comprising build, lint, test and scan; review and policy follow; the change merges; a package is built once as an artifact; it deploys to staging; validation runs; and finally it is promoted to production.
One property of that diagram is worth extracting because it is a genuine engineering principle rather than a convention:
Build once, promote the same artifact.
Rebuilding for each environment means production runs something that was never tested — a different binary, produced from the same source at a different moment, with different dependency resolution. Promoting one immutable artifact through environments is what makes staging validation mean anything.
What you will be able to do
Section titled “What you will be able to do”- Read and write workflow YAML without guessing, including the parts that are genuinely confusing.
- Choose triggers deliberately, and understand why some are security-sensitive.
- Build and test real projects in Python, Node, Go, Java, .NET, containers and infrastructure code.
- Decide between GitHub-hosted and self-hosted runners on evidence rather than on cost alone.
- Deploy to AWS, Azure and Google Cloud without storing long-lived cloud credentials.
- Design reusable workflows and composite actions, and know which one a problem calls for.
- Set permissions to least privilege, pin third-party actions, and reason about supply-chain risk.
- Produce build provenance attestations, and verify them.
The five clusters
Section titled “The five clusters”Actions Fundamentals
Workflows, events, jobs, steps, actions, variables, secrets and contexts.
Continuous Integration
Building and testing Python, Node, Go, Java, .NET, containers and infrastructure code.
Continuous Delivery
Deploying to clouds, containers, Kubernetes, infrastructure and static hosting.
Advanced Actions
Matrices, reusable workflows, custom actions, runners, caching, artifacts and environments.
Actions Security
OIDC, least privilege, action pinning, runner hardening and supply-chain integrity.
GitHub Actions Fundamentals
Section titled “GitHub Actions Fundamentals”The mental model, then every part of it in detail: YAML, events, jobs, steps, actions, variables, secrets, outputs, and the contexts and expressions that connect them. Eleven lessons, mostly beginner, and the foundation everything else assumes.
Continuous Integration
Section titled “Continuous Integration”Eight worked pipelines — Python, Node.js, Go, Java, .NET, Docker, Terraform and Ansible. Each is a real workflow rather than a snippet, covering dependency caching, matrices, artifacts and the pull-request feedback loop.
Continuous Delivery
Section titled “Continuous Delivery”Deploying to AWS, Azure, Google Cloud, container platforms, Kubernetes, infrastructure and GitHub Pages. Every cloud lesson uses OIDC rather than stored credentials, because that is the current recommended architecture and because the alternative ages badly.
Advanced GitHub Actions
Section titled “Advanced GitHub Actions”Matrix builds, reusable workflows, composite actions, custom actions, self-hosted runners, GPU and ARM runners, caching, artifacts, environments and deployment approvals. This is where CI stops being a script and becomes platform engineering.
Secure GitHub Actions
Section titled “Secure GitHub Actions”OIDC in depth and per cloud, eliminating long-lived credentials, least-privilege permissions, action pinning, runner hardening, workflow injection, and software supply-chain integrity with artifact attestations.
Runners: where the work happens
Section titled “Runners: where the work happens”A job needs a machine. GitHub offers two kinds, and the choice has more consequences than it first appears.
GitHub-hosted runners are fresh virtual machines that GitHub provisions per job and destroys afterwards. You choose one with a label:
runs-on: ubuntu-latest # Linux x64runs-on: ubuntu-24.04-arm # Linux ARM64runs-on: windows-latest # Windows x64runs-on: macos-latest # macOS on Apple siliconThe important properties are ephemerality and isolation. Every job starts from a clean image with no memory of previous runs, and nothing a job does can affect another. That is what makes it safe to run a pull request from a stranger.
Self-hosted runners are machines you provide and operate. You get control over hardware, installed software and network position — and you inherit every security and maintenance responsibility that GitHub was handling.
| GitHub-hosted | Self-hosted | |
|---|---|---|
| Provisioning | Per job, automatic | Yours |
| Isolation | Fresh VM every time | Whatever you build |
| Internal network access | None | Yes — often the reason to use them |
| Hardware control | Label selection only | Total |
| Patching and updates | GitHub | You |
| Cost model | Included minutes, then per minute | Your infrastructure |
| Safe for untrusted pull requests | Yes | Only with deliberate isolation |
That last row is the one that matters most and is most often overlooked. A self-hosted runner executing a fork’s pull request is running a stranger’s code on your infrastructure, with whatever network access that machine has. Secure Self-Hosted Runners covers the threat model properly.
The honest guidance: do not self-host to reduce your bill. Self-host when you need hardware, software or network access that hosted runners cannot provide, and budget for the operational work that comes with it.
Security is not a separate cluster
Section titled “Security is not a separate cluster”There is a security cluster, and it goes deeper. But the decisions that determine whether an Actions setup is safe are made in ordinary workflows, so they appear throughout this pillar rather than being deferred.
Five questions recur in almost every lesson:
What can this token do? GITHUB_TOKEN is issued per job with permissions you can — and should —
declare explicitly.
Where do credentials come from? Stored secrets are the obvious answer and usually the wrong one for cloud access. OIDC exchanges a short-lived, signed identity token for temporary cloud credentials, so nothing long-lived exists to leak.
Is any of this input untrusted? A pull request title is attacker-controlled text. Interpolating it directly into a shell command is a script injection.
Whose code is this action? uses: some-org/some-action@v1 runs a third party’s code in your job
with your token. Version references are mutable; commit SHAs are not.
Who can change the workflow? A workflow file change is a privilege change. Owning
.github/workflows/ in CODEOWNERS is a small habit with a large
effect.
None of these require the security cluster to act on. All five are one line each.
What a workflow run actually costs
Section titled “What a workflow run actually costs”Worth understanding early, because it shapes design decisions throughout.
Every job consumes runner minutes. For public repositories, standard GitHub-hosted runners are free. For private repositories, minutes come from the account’s included allowance and are billed beyond it, with multipliers — Windows and macOS runners cost considerably more per minute than Linux.
Three design consequences follow:
Matrix size multiplies everything. A matrix of four language versions across three operating systems is twelve jobs, and each pays its own setup cost. That is often worth it and it should be a decision rather than an accident — see Matrix Builds.
Caching pays for itself quickly. Restoring dependencies rather than downloading them removes a fixed cost from every run, on every branch, forever. Caching covers it.
Concurrency control avoids wasted work. Cancelling superseded runs when someone pushes again stops you paying for results nobody will read.
concurrency: group: ci-${{ github.ref }} cancel-in-progress: trueThat is right for CI and wrong for deployments, where cancelling a half-finished production run is worse than queueing. The deployment lessons return to this repeatedly.
Connections to the earlier pillars
Section titled “Connections to the earlier pillars”This pillar assumes the previous three and links rather than repeating them.
Two connections are worth stating explicitly because they cause real confusion.
A required status check is a workflow job’s result, matched by name. Renaming a job renames the check, and a required check that no longer reports blocks every pull request permanently.
Merge queues need merge_group. A repository with a queue whose workflows only trigger on
pull_request will stall silently — the required check never reports against the merge group.
Common mistakes this pillar prevents
Section titled “Common mistakes this pillar prevents”The failure modes that recur across real repositories, each covered where it belongs:
- Granting broad token permissions because the default was never narrowed.
- Storing cloud access keys as repository secrets where OIDC would eliminate them entirely.
- Running deployments on every branch because the trigger was never filtered.
- Interpolating untrusted input into shell commands, turning a pull request title into a command.
- Using a mutable action reference for third-party code, so an upstream compromise reaches you automatically.
- Confusing cache and artifacts — one is a performance optimisation that may vanish, the other is output you intended to keep.
- Rebuilding for production instead of promoting the artifact that was tested.
- Running untrusted pull requests on self-hosted runners with network access.
- Copying the same workflow into thirty repositories instead of calling a reusable one.
- Omitting concurrency control on deployments, allowing two production runs simultaneously.
Every one of these is cheap to avoid at the point of writing and expensive to discover later.
Four questions that read any workflow
Section titled “Four questions that read any workflow”A practical skill, since you will read far more workflows than you write. Answer these four and you know what a workflow does and what it risks, usually without reading a single step.
What triggers it? The on: block. This tells you when it runs and — critically — whether it runs
on pull requests from strangers.
What can it do? The permissions: block, or its absence. No block means the repository default,
which may be broad.
What is the job graph? Job names plus needs: gives the shape: what runs in parallel, what waits,
what gates what.
Whose code does it run? Every uses: line that is not actions/* is a third party executing
inside the job with your token in reach.
That last question is the one most often skipped and the one with the largest consequences. A workflow with eight third-party actions has eight suppliers, and none of them is reviewed by anyone in your organisation unless someone decided to.
How to work through it
Section titled “How to work through it”The clusters are ordered deliberately, and Fundamentals genuinely is a prerequisite — the rest of the pillar uses contexts, expressions and job dependencies without re-explaining them.
After that, the path depends on what you are doing:
- Setting up CI for a project — Fundamentals, then the language lesson in Continuous Integration that matches your stack.
- Deploying to a cloud — read OIDC before the deployment lesson. The cloud lessons assume it, and understanding the trust model first makes them much shorter.
- Standardising CI across many repositories — Reusable Workflows is the article to start from.
- Auditing an existing setup — the Security cluster stands alone reasonably well, and Workflow Security is the broadest single page.
Prerequisites
Section titled “Prerequisites”| You should be able to | Covered in |
|---|---|
| Commit, branch and push | Git Fundamentals |
| Explain what a pull request stores | Pull Requests Explained |
| Describe required checks and branch policy | Required Reviews |
Use gh for run and workflow inspection | gh run · gh workflow |
| Choose a credential type deliberately | GitHub API Authentication |
You do not need prior CI/CD experience. You do need a repository you can safely break — every exercise in this pillar uses a disposable one.
A platform that moves
Section titled “A platform that moves”GitHub Actions changes faster than anything else this site covers. Action major versions advance, runner labels are added and retired, and features arrive in preview.
Two consequences shape how this pillar is written.
Action versions are centralised. Every action reference in this site is registered in
src/lib/action-versions.ts and checked against its current release by npm run check:actions. That
turns “are these examples current?” from a manual audit into one command — which matters, because
during this pillar’s research several actions were found to be four or more major versions ahead
of what contemporary tutorials show.
Availability is stated, not assumed. Larger runners, GPU runners, some environment protections and some attestation features depend on plan, repository visibility or organisation policy. Where that is true, the page says so and links GitHub’s own documentation rather than implying universal availability.
Pages documenting vendor-controlled behaviour are marked platform-sensitive in their frontmatter, which is the list to re-check when something on your screen disagrees with something here.
Begin: GitHub Actions FundamentalsContinue to Git Security & DevSecOps
Section titled “Continue to Git Security & DevSecOps”You’ve learned how to automate builds, tests, releases and deployments. Next, learn how to protect repositories, credentials, source code, dependencies, build systems and release artifacts.
The two pillars overlap deliberately and divide cleanly: this one secures the automation, and Pillar 5 secures the lifecycle the automation sits inside — the repository it reads from, the credentials it must never encounter, the dependencies it builds against, and whether anyone downstream can verify what it produced.
Continue to Git Security & DevSecOps