Technology handbook
GitHub Actions
Workflows, triggers, jobs, runners, caching, reusability and supply-chain security.
- Topics
- 24
- Sections
- 4
Workflow fundamentals
Workflow files
Define triggers, jobs and steps in a versioned YAML file.
- Triggers, jobs and steps live in a versioned YAML file beside the code.
- The file on the triggering ref is the one that runs, not the one on the default branch.
- A syntax error stops the workflow being registered rather than failing a run.
Events and triggers
Run on push, pull request, schedule, manual dispatch or another workflow.
- A run starts from a push, a pull request, a schedule, a manual dispatch or another workflow.
- Each event exposes a different payload, so conditions cannot be shared blindly.
- A scheduled workflow only runs from the default branch.
Jobs and steps
Group work into jobs and sequence the commands inside each.
- Work is grouped into jobs, and commands are sequenced inside each.
- Every job gets a fresh machine, so nothing carries across without an artifact.
- Steps in a job share a filesystem and a working directory.
Runners
Choose a hosted or self-hosted runner and the operating system.
- A hosted or self-hosted runner is chosen along with its operating system.
- Hosted runners are discarded after the job, which is what makes them clean.
- Shell syntax differs per operating system, so a matrix needs portable commands.
Expressions and contexts
Read event, job and step data to make decisions.
- Event, job and step data are read to make decisions inside the workflow.
- Contexts are evaluated before the shell runs, so a value can be substituted into a command.
- Interpolating untrusted event text into a run step is a script injection.
Conditions
Run a job or step only when a condition holds.
- A job or step runs only when its condition holds.
- A step is skipped rather than failed, which keeps the job green.
- A condition on failure needs an explicit always or failure call to run at all.
Reuse & environments
Composite actions
Package repeated steps as one action inside the repository.
- Repeated steps are packaged as one action inside the repository.
- It keeps the steps in the same repo rather than in a shared workflow.
- A composite action cannot itself use a matrix.
Reusable workflows
Call a shared workflow with inputs and secrets.
- A shared workflow is called with inputs and secrets.
- Secrets have to be passed explicitly or inherited; they do not flow automatically.
- The called workflow runs its own jobs, which is what makes it more than a step.
Environments
Gate a deployment behind approval and environment-scoped secrets.
- A deployment is gated behind approval and environment-scoped secrets.
- The approval pauses the job rather than failing it.
- Environment secrets override repository ones for that job only.
Deployment jobs
Sequence build, test and deploy with explicit dependencies.
- Build, test and deploy are sequenced with explicit dependencies.
- Without a dependency the deploy job races the tests it should follow.
- Restricting the deploy to the default branch is a condition, not an assumption.
Variables and secrets
Separate configuration from credentials at the right scope.
- Configuration and credentials are separated at the scope each belongs to.
- A variable is readable in logs; a secret is masked.
- A secret is unavailable to a workflow triggered from a fork.
Self-hosted runner isolation
Keep untrusted code away from a persistent runner.
- Untrusted code is kept away from a runner that persists between jobs.
- A persistent runner carries whatever the previous job left behind.
- A public repository with a self-hosted runner lets any fork run code on it.
Security
GITHUB_TOKEN permissions
Grant the minimum scope a workflow needs and default to read.
- A workflow is granted the minimum scope it needs, defaulting to read.
- The token is issued per run and expires when the run ends.
- Write permission on contents is what lets a compromised step push to the repository.
Pinning actions
Pin third-party actions to a commit rather than a moving tag.
- A third-party action is pinned to a commit rather than a moving tag.
- A tag can be repointed, so it is not a version, only a name.
- A pinned action needs a deliberate update path or it goes stale.
pull_request_target risks
Understand why this trigger must never run untrusted code.
- This trigger runs with repository secrets and the base branch's permissions.
- Checking out the fork's code under it hands those secrets to a contributor.
- It exists for workflows that must label or comment, not build the change.
Secret handling
Avoid printing secrets and recognize where they can leak.
- Secrets are kept out of logs and away from the places they leak.
- Masking covers exact matches, so a transformed secret prints in clear.
- A secret passed as a command argument is visible to anything reading the process list.
OIDC to cloud providers
Exchange a short-lived token for cloud access instead of storing keys.
- A short-lived token is exchanged for cloud access instead of storing long-lived keys.
- There is no stored credential left to leak or rotate.
- The trust policy must pin the repository and ref, or any repository can assume the role.
Dependency review
Block a pull request that introduces a vulnerable dependency.
- A pull request introducing a vulnerable dependency is blocked.
- The check runs on the change rather than on the whole tree.
- It reads the manifest, so a dependency pulled in at runtime is invisible to it.
Sources
- GitHub Actions official documentation