Skip to content

Advanced GitHub Actions

6 min readGitHub Actions & CI/CD · Advanced Actions

At some point a repository stops having a workflow and starts having a CI/CD system — several repositories sharing standards, pipelines that must not be copy-pasted, runners chosen for hardware rather than convenience, and deployments that require someone’s approval.

That transition is what this cluster is about.

Start with Matrix Builds

Rather than a feature list, the honest framing is the problem that makes each feature necessary.

ProblemLesson
“We need to test across five versions and three platforms”Matrix Builds
“Thirty repositories have the same 80-line workflow”Reusable Workflows
“These six steps repeat in every job”Composite Actions
“We need something no existing action does”Custom Actions
“Our build needs hardware or network access GitHub cannot give us”Self-Hosted Runners
“Model training needs a GPU”GPU Runners
“We ship ARM64 and only test x64”ARM Runners
“Every run spends four minutes downloading dependencies”Caching
“The build output disappears when the job ends”Artifacts
“Staging and production need different configuration”Environments
“Production deployments need a human decision”Deployment Approvals

If none of those describe a problem you have, this cluster is premature. Adding reusable workflows to one repository, or a self-hosted runner to a project that builds fine on hosted ones, is complexity without a corresponding benefit.

Three lessons cover reuse, and choosing between them is the most common question in this cluster.

Centralised CI across repositories

Three application repositories each call one shared reusable CI workflow, which contains build, test and scan jobs. Below, an application workflow calls a reusable deployment workflow, which uses OIDC to authenticate to a cloud provider.

Application repo AApplication repo BApplication repo CReusable CI workflowBuildTestScanApplication workflowReusable deployment workflowOIDCCloud provider

That upper shape is platform engineering in one picture: standards defined once, consumed by many, versioned like code. The lower shape is the same idea applied to deployment, where the reusable workflow also owns the cloud trust relationship — so individual application repositories never handle credentials at all.

The distinction that causes the most confusion, stated once here and covered fully in both lessons.

Reusable workflowComposite action
Called fromA job (uses: at job level)A step (uses: inside steps:)
Can define multiple jobsYesNo
Chooses its own runnerYes — each job has runs-onNo — runs inside the caller’s job
Defined inA workflow YAML fileaction.yml plus its implementation
Location.github/workflows/Any directory, or its own repository
SecretsPassed explicitly or inheritedPassed as inputs
Typical useA complete CI or deployment pipelineA repeated sequence of steps

The one-line test: if it needs its own runner, it is a workflow. If it is a sequence of steps inside someone else’s job, it is a composite action.

You should be able toCovered in
Write and debug a workflowFundamentals
Build a CI pipeline for a real projectContinuous Integration
Pass values between jobsOutputs
Reason about permissionsLeast-Privilege Permissions

The self-hosted runner lessons additionally assume you can provision and secure a machine. If that is not you, they are still worth reading — knowing what self-hosting costs is how you decide not to do it.

  1. Lesson 1: 01. Matrix BuildsRun one job across many configurations — matrix axes, include and exclude, fail-fast, max-parallel, dynamic matrices from JSON, and keeping required checks stable.Intermediate4 min read
  2. Lesson 2: 02. Reusable WorkflowsCall one workflow from another — workflow_call inputs, secrets and outputs, versioning, nesting limits, and how reusable workflows differ from composite actions.Intermediate → Advanced5 min read
  3. Lesson 3: 03. Composite ActionsPackage a sequence of steps as a reusable action — action.yml structure, inputs and outputs, the shell requirement, local versus published, and the limits to know.Intermediate4 min read
  4. Lesson 4: 04. Custom ActionsWrite your own action — JavaScript, Docker container and composite types compared, action.yml metadata, the toolkit, bundling, testing, versioning and publishing.Advanced5 min read
  5. Lesson 5: 05. Self-Hosted RunnersRun workflows on your own machines — when self-hosting is justified, installing a runner, labels, runner groups, ephemeral runners and autoscaling with the Actions Runner Controller.Advanced5 min read
  6. Lesson 6: 06. GPU RunnersRun accelerated workloads in CI — GitHub-hosted GPU runner availability, self-hosted GPU fleets, verifying the device, container access and controlling the cost.Advanced4 min read
  7. Lesson 7: 07. ARM RunnersBuild natively on ARM with GitHub Actions — available Linux and Windows ARM runner labels, native versus emulated builds, and multi-architecture container images.Intermediate4 min read
  8. Lesson 8: 08. CachingMake workflows fast with actions/cache — key design, restore-keys fallbacks, branch scoping, size quotas and eviction, and the cache-poisoning risk to avoid.Intermediate5 min read
  9. Lesson 9: 09. ArtifactsUpload, download and manage workflow artifacts — retention, size and storage cost, passing output between jobs, immutability, and what must never be uploaded.Intermediate5 min read
  10. Lesson 10: 10. EnvironmentsUse environments to gate deployments — protection rules, environment secrets and variables, branch and tag restrictions, deployment URLs and history.Intermediate4 min read
  11. Lesson 11: 11. Deployment ApprovalsRequire human approval before a deployment — reviewers, wait timers, branch restrictions, what approval does and does not verify, and designing gates people read.Intermediate4 min read

Both store files between jobs. Confusing them produces both broken pipelines and real security problems, so the distinction appears here and in both lessons.

CacheArtifact
PurposeMake future runs fasterPreserve output
May vanishYes — evicted at any timeNo, until retention expires
A miss isNormal; the job continuesA failure
ScopeShared across branches by rulesThe workflow run
ContentsReproducible dependenciesBuild results, reports
Safe for sensitive dataNoNo

A cache is an optimisation you must be able to lose. An artifact is a result you meant to keep.

Using a cache for build output means a run that misses produces nothing. Using an artifact for dependencies means paying storage for something you could re-download.

Three mechanisms, and the decision is usually clear once framed as a question about scope.

A composite action when the same steps repeat inside jobs. Setting up a toolchain, configuring credentials, a build-and-verify sequence. It runs in the caller’s job and shares its runner.

A reusable workflow when the same jobs repeat across repositories. An entire CI pipeline, a deployment sequence. It brings its own jobs and runners.

A custom action when neither fits — you need behaviour no combination of existing steps provides, or you want to distribute something to people outside your organisation.

The progression matters: most teams should exhaust composite actions before writing a reusable workflow, and exhaust both before writing a JavaScript or Docker action. Each step up costs more to maintain, and the maintenance is permanent.

Reusable workflows are presented as unambiguously good, and they carry real trade-offs worth stating.

A shared workflow is a shared dependency. A breaking change to it breaks every consumer simultaneously. Version it, and let consumers pin.

It is also shared attack surface. A compromised shared workflow reaches every repository that calls it, with whatever secrets they pass. This is the strongest argument for naming secrets explicitly rather than using secrets: inherit across an organisation.

It hides what runs. A repository whose entire pipeline is uses: org/workflows/ci.yml@v2 is readable in one line and opaque in every other sense. Someone auditing it must go elsewhere.

It resists local variation. The team that needs one extra step now needs the shared workflow to grow an input, or to stop using it. Shared workflows accumulate inputs for exactly this reason.

None of that argues against centralising — for thirty repositories with identical needs it is clearly right. It argues for doing it deliberately, with versioning, and for keeping the shared thing as small as the shared need actually is.

The decision most often made for the wrong reason.

NeedAnswer
Standard Linux, Windows or macOS buildsGitHub-hosted
ARM64 builds and testsGitHub-hosted ARM labels
More cores or memory than standardLarger runners — Team and Enterprise Cloud
GPU workloadsLarger GPU runners where available, or self-hosted
Access to a private networkSelf-hosted, necessarily
Specific licensed softwareSelf-hosted
Unusual hardwareSelf-hosted
“It would be cheaper”Reconsider

The last row is the one worth arguing about. Self-hosting shifts cost from a bill to an operational burden: patching, monitoring, scaling, and a security posture that GitHub was previously providing. For an organisation that already operates infrastructure, that may be a good trade. For a team that does not, it usually is not — and the security consequences of a badly-run runner are considerably more expensive than the minutes it saved.

The Security cluster is the natural continuation, and it is not optional for anything in this one. Self-hosted runners, custom actions and reusable workflows each expand what a compromise could reach — a shared workflow is shared attack surface, and a runner you operate is a machine you must defend.## Prerequisites, restated

This cluster assumes you can write and debug a workflow without reference, and that you have a real pipeline to improve. Every lesson starts from a problem rather than a feature, and the problems only make sense once you have run into them.

If you are here before building anything, Fundamentals and then a pipeline from Continuous Integration will make this cluster far more useful.

Every feature in this cluster adds something to maintain. A matrix is more configuration than one job; a reusable workflow is a shared dependency; a self-hosted runner is a machine to patch.

The useful discipline is to add each one in response to a problem you can name, and to be willing to remove it when the problem goes away. A repository carrying a reusable workflow, three composite actions and a self-hosted runner for a pipeline that builds one small service has more machinery than it has need.

Begin: Matrix Builds