GitHub Actions Foundations, Automation Model, and CI/CD Concepts: Core Concepts and Mental Model
GitHub Actions is easiest to understand when you stop thinking of it as “a YAML file that runs commands” and instead model it as an event-driven execution system. A repository event selects workflow configuration at a particular revision; GitHub creates a run and jobs; each job is assigned to a runner; ordered steps execute there; and only then do checks, artifacts, deployments, or external systems record outcomes. This lesson builds that causal model before adding more syntax.
Learning objectives
- Explain the event → workflow revision → run → job → runner → step → evidence chain without collapsing distinct states into “the workflow ran.”
- Distinguish a workflow, workflow run, run attempt, job, step, action, runner, check, artifact, environment, deployment, and external target.
-
Use read-only runtime evidence such as
GITHUB_SHA,GITHUB_REF,GITHUB_RUN_ID, andGITHUB_RUN_ATTEMPTto identify what actually executed. - Explain why steps in one job can share a filesystem while separate GitHub-hosted jobs must be treated as separate machines.
- Relate GitHub Actions automation to CI, continuous delivery, deployment authorization, least privilege, reproducibility, and auditability.
1. The problem: automation without an execution model becomes guesswork
A team can copy a working workflow from a blog and still be unable to answer basic operational questions: Which commit caused this run? Which version of the workflow was evaluated? Did a job wait for a runner or fail after assignment? Did a green build produce the expected artifact? Did an approved deployment actually make the external service healthy? Those are different questions because they are owned by different layers.
The first production skill is therefore not memorizing YAML. It is learning to follow evidence through the system. If a run is reproducible, another engineer should be able to identify the triggering event, exact source/workflow revision, permissions, runner context, step code, outputs, and any external side effects.
2. The event-driven mental model
A workflow starts with intent. The intent may be a repository event such as a push, a manual dispatch, a schedule, an API dispatch, or another supported event. GitHub evaluates the workflow definition associated with the event/ref rules, creates a workflow run, evaluates jobs and conditions, queues eligible jobs, assigns runners, then executes steps on those runners.
flowchart TD A[Repository event or manual intent] --> B[Workflow definition at a revision] B --> C[Workflow run ID + attempt] C --> D[Job graph and conditions] D --> E[Queue and runner assignment] E --> F[Ordered steps: scripts or actions] F --> G[Outputs, checks, logs, artifacts] G --> H[Environment / deployment records] H --> I[External target health and governance evidence]
The arrows matter. For example, a workflow file can be valid YAML but never selected by the event filter. A job can be created but remain queued because no matching runner is available. A deployment job can finish after an API request even though the external service later fails health checks. Diagnosis becomes much faster when you ask, “At which arrow did the expected state stop advancing?”
3. Core nouns: define the objects before using them
| Object | Mental model | Evidence you can inspect |
|---|---|---|
| Workflow |
A versioned YAML automation definition stored under
.github/workflows.
|
File path, repository revision, triggers, permissions, job definitions. |
| Event | The activity or explicit request that may select a workflow. | Event name, payload, ref, actor, source repository. |
| Run | One workflow execution created for an event/dispatch. |
GITHUB_RUN_ID, run number, event, head SHA,
status/conclusion.
|
| Run attempt | A rerun of the same run identity. |
GITHUB_RUN_ATTEMPT; first execution starts at
1.
|
| Job | A set of ordered steps scheduled onto one runner. | Job name, dependencies, queue/start/end times, runner, conclusion. |
| Step | An ordered unit inside a job; a shell command or an action invocation. | Step name, command/action revision, log, exit status, outputs. |
| Action | Reusable code invoked by a step. | Repository/path, immutable revision or release reference, inputs, runtime. |
| Runner | The compute environment that executes one job. | OS label, image metadata, architecture, tool versions, runner name. |
| Artifact / cache | GitHub-managed stored files; artifacts are evidence/results, caches are performance aids. | Name, producing run/SHA, retention, digest/metadata where supported. |
| Environment / deployment | GitHub records used to control or report deployment intent and status. | Environment name, approval/protection state, deployment status. |
| External target | The cloud, registry, server, cluster, or service changed by the workflow. | Provider-side deployment/resource ID, digest, health check, logs. |
This vocabulary prevents a common communication failure. Saying “Actions failed” is too broad. Saying “the run was created, the build job queued for three minutes, the hosted runner started, and step 4 exited 1 before any artifact was produced” is actionable.
4. Revision identity: the run is not just a branch name
Branches and tags are human-friendly references; commits are
immutable identities. GitHub exposes event-dependent values such as
GITHUB_SHA, GITHUB_REF, and
GITHUB_REF_NAME. The exact meaning of
GITHUB_SHA depends on the event type, so production
workflows should record it rather than infer it from a branch label.
Also separate the event SHA from a later checked-out SHA. In this chapter's minimal workflow we deliberately do not run a checkout action. That proves an important boundary: the hosted runner does not automatically contain your repository source. In later chapters, if a workflow checks out another ref, the workspace commit can differ from the event's original SHA; both identities then need evidence.
printf 'event=%s\n' "$GITHUB_EVENT_NAME"
printf 'ref=%s\n' "$GITHUB_REF"
printf 'sha=%s\n' "$GITHUB_SHA"
printf 'run_id=%s\n' "$GITHUB_RUN_ID"
printf 'run_attempt=%s\n' "$GITHUB_RUN_ATTEMPT"
printf 'workflow=%s\n' "$GITHUB_WORKFLOW"
5. Run ID, run number, and run attempt answer different questions
GITHUB_RUN_ID uniquely identifies a workflow run within
a repository and remains the same when that run is rerun.
GITHUB_RUN_ATTEMPT starts at 1 and increments for
reruns. GITHUB_RUN_NUMBER is the sequence number for
that workflow. Preserve the ID and attempt together when comparing
logs because a rerun can execute changed external conditions even
when the logical run ID is unchanged.
Stable identity used to correlate UI, API, jobs, logs, and reruns.
Which execution of that run produced this evidence.
Revision identity that the event associates with the run.
Completed result such as success, failure, cancelled, or skipped—not a deployment health guarantee.
6. Runner state: one job, one execution environment
For normal GitHub-hosted runners, a job begins on newly provisioned hosted compute. Steps within that job execute on the same runner and can share files through its workspace. A different job must be treated as a separate execution environment; do not expect files created by one hosted job to exist in another.
Current runner-image mappings change over time. At this lesson's
verification date, ubuntu-latest maps to Ubuntu 24.04,
windows-latest to Windows Server 2025, and
macos-latest to macOS 26 Arm64 in the runner-images
project. The lab pins ubuntu-24.04 so the OS family
does not silently move when the -latest alias migrates.
printf 'runner_os=%s\n' "$RUNNER_OS"
printf 'runner_arch=%s\n' "$RUNNER_ARCH"
uname -a
python3 --version
Pinning the OS label does not freeze every preinstalled tool. Hosted images are still updated. If a build depends on a particular compiler, runtime, or package manager version, set that toolchain explicitly in later workflow design rather than relying on whatever happens to be preinstalled.
7. Permissions are evaluated configuration, not decoration
GitHub creates a GITHUB_TOKEN for jobs, but its usable
permissions depend on repository/organization defaults, event trust,
and explicit workflow/job configuration. If you declare any
permissions, unspecified scopes become none. Chapter
01's observation-only workflows use
permissions: {} because they do not need repository API
access.
permissions: {}
This is a deliberately strong teaching default: start with no repository mutation capability, then add a narrow permission only when a later operation proves it needs one. A build step that only computes local files should not silently carry write authority to issues, contents, packages, or deployments.
8. Automation, CI, continuous delivery, and deployment are related—not synonyms
| Practice | Main question | Typical Actions evidence | What it does not prove |
|---|---|---|---|
| Repository automation | Can repetitive repository work happen consistently? | Run/job/step result, API mutation record. | That software was built or releasable. |
| Continuous integration | Does this exact change integrate and satisfy automated checks? | Build/test/lint results tied to SHA. | That production changed. |
| Continuous delivery | Is a verified artifact kept in a deployable state? | Immutable artifact/digest, gate outcome, release candidate evidence. | That deployment was approved or healthy. |
| Continuous deployment | Should verified change automatically reach a target? | Deployment authorization, provider response, target health. | Long-term service health after rollout. |
A mature pipeline promotes evidence rather than merely chaining commands. Build/test evidence should identify the exact source; deployment should use the verified artifact rather than silently rebuilding; post-deploy health should come from the target system, not from wishful interpretation of a successful API call.
9. Read-only inspection first
Before changing repository settings or introducing credentials,
inspect the current workflow/run state. In the GitHub web UI, open
Actions → a run → a job and locate the event,
branch/ref, commit, job name, runner setup, and step conclusions.
For CLI users, gh run list and
gh run view provide another read-only surface after
normal local authentication.
The evidence questions are more important than the UI path:
- Which event created this run?
- Which immutable SHA is associated with the event?
- Which run ID and attempt are these logs from?
- Which runner label/image executed the job?
- What repository permission did the job actually need?
- Which step first changed from expected to unexpected state?
- Did anything outside GitHub change, and how is that proved independently?
10. Foundation mistakes to eliminate early
- “The YAML parses, therefore CI works.” Syntax can be valid while the trigger never selects the workflow or a condition skips the job.
- “The branch name tells me exactly what ran.” Record the event SHA and workflow/run identity.
- “All jobs share one machine.” Hosted jobs are separate execution environments; only steps within a job share that job's runner filesystem.
- “A green step means deployment succeeded.” It proves only that the step concluded successfully; target health needs target evidence.
- “The runner can keep important state for the next run.” Hosted compute is ephemeral; durable evidence belongs in explicit storage/services, not an incidental workspace.
- “More token permission is harmless.” Unneeded authority increases blast radius if a command, dependency, or input is compromised.
11. Micro-lab: predict a run before creating one
Do this on paper or in a local note before Lesson 2. Imagine a
workflow triggered by push to main,
containing one job on ubuntu-24.04 and three shell
steps. Predict these facts before any run exists:
- A push to another branch should not create a matching run if the workflow's branch filter excludes it.
- A selected push should create one run identity with attempt 1.
- The single job should be queued and then assigned to one hosted runner.
- All three steps should see files created by earlier steps in that same job.
- A second job would not automatically see those files.
-
With
permissions: {}, the job should not rely on repository write access.
Lesson 2 turns these predictions into a disposable live experiment. The goal is not merely to make a green badge; it is to prove each predicted state independently.
Knowledge check
A step prints “deployment request accepted” and exits 0. What is proved?
Only that the step completed successfully and printed that message. External deployment completion and target health require independent provider/target evidence.
Why preserve both GITHUB_RUN_ID and
GITHUB_RUN_ATTEMPT?
The run ID identifies the logical run while the attempt identifies which rerun produced a particular set of logs and side effects.
Can two separate GitHub-hosted jobs safely assume they share a
file created in $GITHUB_WORKSPACE?
No. Treat each hosted job as separate compute. Use an explicit cross-job mechanism later, such as outputs or artifacts, when data must cross that boundary.
Why use permissions: {} in the Chapter 01
observation workflow?
The workflow only needs local execution and runtime metadata, so repository API authority is unnecessary. Starting with no permissions makes least privilege visible.
What is the first identity to record when someone says “the main branch workflow failed”?
Record the actual run/event identity—especially event type, exact SHA, run ID, and attempt—before reasoning from a moving branch name.
Official references and version notes
- Understanding GitHub Actions — current component model for workflows, events, jobs, steps, actions, and runners.
- Workflow syntax for GitHub Actions — authoritative workflow keys, permissions, jobs, runner selection, and manual dispatch syntax.
- Variables reference — definitions of GITHUB_SHA, GITHUB_REF, GITHUB_RUN_ID, GITHUB_RUN_ATTEMPT, and related default variables.
- GitHub-hosted runners reference — current hosted-runner labels, VM behavior, hardware, and image caveats.
- Billing and usage — current availability, public-repository standard-runner usage, private-repository quotas, and usage boundaries.
Version-sensitive statements were rechecked against primary GitHub
documentation and GitHub-maintained runner-image information on
2026-09-09. The executable Chapter 01 examples
pin ubuntu-24.04; at verification time
ubuntu-latest maps to Ubuntu 24.04, but hosted images
and preinstalled tools continue to update. The labs use no
external actions and no secrets, and explicitly set
permissions: {}.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.