GitHub Actions Foundations, Automation Model, and CI/CD Concepts: Guided Hands-On Workflow
This lesson turns the mental model into evidence. You will create one deliberately small workflow in a disposable repository, trigger it once by push and once manually, then compare the two runs using their event, ref/SHA, run ID, attempt, job, runner, and logs. The workflow performs no deployment, uses no secret, invokes no external action, and requests no repository token permissions.
Learning objectives
-
Create a minimal workflow that supports both
pushandworkflow_dispatchwithout introducing third-party dependencies. - Predict and verify event/ref/SHA, run ID/attempt, job/step state, runner identity, and local workspace behavior.
- Use selected event-payload fields safely instead of dumping complete contexts into logs.
- Compare a repository push run with a manual run and explain which state is equal, different, or event-dependent.
- Clean up only the disposable resources created by the lab and preserve a small evidence packet before deletion.
1. Lab scope and safety boundary
Use a throwaway repository such as gha-ch01-lab. A
public repository is the simplest no-cost path for standard
GitHub-hosted runners; a private repository also works if your
account has available Actions minutes. Do not use an
employer/customer repository, a protected production branch, a
repository with real deployment secrets, or a self-hosted runner.
| Item | Required lab value |
|---|---|
| Repository | Disposable personal training repository only. |
| Default branch | main. |
| Workflow path | .github/workflows/ch01-foundations.yml. |
| Runner | ubuntu-24.04. |
| Repository token permissions | permissions: {}. |
| Secrets | None. |
| External side effects | None; no packages, releases, deployments, cloud, registry, or issue/PR mutation. |
| Evidence | Run IDs, attempts, event/ref/SHA, runner metadata, job/step conclusions, selected event fields. |
2. Preflight: inspect before changing anything
- Confirm the repository is disposable and owned/controlled by you.
- Confirm
mainis the default branch. - Open the repository's Actions tab and note whether it currently has any runs.
- Check Settings → Actions → General only if you need to understand why Actions is disabled. Do not broaden organization/repository policy just to make the lab work; use another disposable repository if policy is intentionally restrictive.
-
Record the current commit SHA on
main. This is your “before” revision.
If you use Git locally, a read-only identity check is:
git branch --show-current
git rev-parse HEAD
git status --short
3. Create the smallest useful workflow
Create .github/workflows/ch01-foundations.yml with the
following content. The workflow intentionally does not use
actions/checkout; this makes runner state and event
identity easier to see.
name: Chapter 01 - Foundations Evidence
on:
push:
branches: [main]
workflow_dispatch:
permissions: {}
jobs:
observe:
name: Observe event and runner state
runs-on: ubuntu-24.04
steps:
- name: Record immutable run identity
shell: bash
run: |
set -euo pipefail
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"
- name: Inspect selected event fields safely
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import json, os
with open(os.environ['GITHUB_EVENT_PATH'], encoding='utf-8') as fh:
event = json.load(fh)
keep = {
'event_name': os.environ['GITHUB_EVENT_NAME'],
'ref': event.get('ref'),
'before': event.get('before'),
'after': event.get('after'),
'repository': event.get('repository', {}).get('full_name'),
}
print(json.dumps(keep, indent=2, sort_keys=True))
PY
- name: Prove same-job filesystem continuity
shell: bash
run: |
set -euo pipefail
printf '%s\n' "$GITHUB_RUN_ID:$GITHUB_RUN_ATTEMPT:$GITHUB_SHA" > evidence.txt
test -s evidence.txt
cat evidence.txt
- name: Write a safe job summary
shell: bash
run: |
{
echo '## Chapter 01 evidence'
echo
echo "- Event: \`$GITHUB_EVENT_NAME\`"
echo "- Ref: \`$GITHUB_REF\`"
echo "- SHA: \`$GITHUB_SHA\`"
echo "- Run: \`$GITHUB_RUN_ID\`, attempt \`$GITHUB_RUN_ATTEMPT\`"
echo "- Runner OS: \`$RUNNER_OS\` / \`$RUNNER_ARCH\`"
} >> "$GITHUB_STEP_SUMMARY"
4. Predict the push run before committing
Write these predictions in a local note before pushing:
-
The commit that adds the workflow to
mainshould match thepushbranch filter and create a run. - The run's event should be
push. - The first attempt should be 1.
-
The job should use a new
ubuntu-24.04hosted runner. - The first two steps should print selected metadata only.
-
The third step should create and read
evidence.txtbecause steps in the same job share the job filesystem. - The workflow should need no repository write permission and no secret.
Then commit and push. A simple local sequence is:
git add .github/workflows/ch01-foundations.yml
git commit -m "add Chapter 01 Actions evidence workflow"
git push origin main
If your repository uses a pull-request-only policy, do not bypass
it. Use the repository's normal allowed path or a separate
disposable repository where you can safely push to
main.
5. Inspect the push run as an evidence chain
Open Actions → Chapter 01 - Foundations Evidence → latest run. Record, do not merely glance at:
Expected: push.
Expected: the exact commit that added/changed the workflow.
A new run ID with attempt 1.
Observe event and runner state.
Ubuntu 24.04 hosted execution plus current image/tool metadata in setup logs.
Expected success if every shell command exits 0.
Compare the workflow's printed SHA with the commit shown by the run UI. If they differ, stop and explain the event semantics before continuing. Do not “fix” a mismatch by changing printed evidence.
6. Trigger a second run manually
The workflow_dispatch trigger allows a manual run. The
workflow file must exist on the default branch for this trigger to
be available. Use the Actions UI and select
Run workflow. Keep the selected ref explicit; for
this first lab, choose main.
Before clicking, predict:
-
The event name changes from
pushtoworkflow_dispatch. - A new run ID is created; this is not attempt 2 of the push run.
-
GITHUB_RUN_ATTEMPTshould again start at 1 for the new run. -
The selected ref/SHA should resolve to the chosen
mainrevision at dispatch time. - The same workflow/job/step definitions execute, but the event payload shape differs.
Run it and verify each prediction independently.
7. Compare the two runs instead of calling both “green”
| Evidence | Push run | Manual run | Interpretation |
|---|---|---|---|
| Event | push |
workflow_dispatch |
Different trigger intent. |
| Run ID | Unique ID A | Unique ID B | Two separate workflow runs. |
| Attempt | 1 | 1 | Neither is a rerun. |
| Workflow name | Same | Same | Same automation definition surface. |
| SHA | Push event's commit | Chosen ref's resolved commit | Always record; do not assume. |
| Job/steps | Same definitions | Same definitions | Execution context differs even when YAML is equal. |
8. Optional read-only CLI inspection
If GitHub CLI is already installed and authenticated on your workstation, you can correlate UI evidence without exposing tokens inside the workflow:
gh run list --workflow ch01-foundations.yml --limit 10
gh run view RUN_ID
gh run view RUN_ID --log
For a compact API evidence file, after substituting your own disposable repository and run ID:
gh api repos/OWNER/REPO/actions/runs/RUN_ID \
--jq '{id,run_attempt,event,head_branch,head_sha,status,conclusion,html_url}' \
> run-evidence.json
Local gh authentication is outside the workflow and
should follow your normal credential storage. Never paste a PAT into
the workflow YAML or commit it to the repository.
9. Map the experiment to automation, CI, and CD
This workflow is automation because it executes repeatable work on events. It is only a tiny CI skeleton: it records revision identity and proves the runner can execute steps, but it does not yet check out source, compile, test, lint, or publish a result artifact. It is not continuous delivery or deployment because there is no releasable artifact, deployment authorization, or external target.
10. Challenge: find the owning layer
Without changing permissions or adding actions, change the
workflow's push filter temporarily to a branch name that does not
match your next push. Push a harmless documentation commit to
main. If no run is created, identify the cause
correctly: the failure is at
event/workflow selection, not runner capacity or
shell execution. Restore the filter to main afterward
and verify the next harmless push creates a run again.
This challenge is optional if repository policy makes extra pushes undesirable. The conceptual requirement is to distinguish “no run selected” from “run selected but job failed.”
11. Verification and cleanup
- Two controlled runs exist: one push and one manual dispatch.
- Each has a unique run ID and attempt 1.
- The observed SHA/ref/event fields are recorded.
- Runner and step evidence is visible.
- No secret or full context dump appears in logs.
- No external action, package, release, deployment, cloud, registry, or self-hosted runner was used.
-
No repository token permission was required beyond
permissions: {}.
Before deleting anything, save the run URLs/IDs and a short comparison note. Cleanup can mean keeping the disposable repository for the rest of the course, disabling/removing this single workflow, or deleting the throwaway repository if you are finished. Delete only the exact resource you created; never use ambiguous “latest repository/run” cleanup automation.
Knowledge check
Why are the push run and manual run not attempts 1 and 2 of the same run?
They are created by separate trigger events, so GitHub creates separate run IDs. An attempt increments only when the same run is rerun.
The workflow is green. Has it proved the repository source builds?
No. This lab intentionally never checks out or builds source. It proves the observed workflow execution path only.
Why print selected event fields instead of the entire
github context?
Least disclosure. Full contexts can include sensitive or unnecessary values; evidence should include only the fields required to explain execution.
A push to feature/demo creates no run because the
workflow filters only main. Which layer owns the
behavior?
Event/workflow selection. No runner or step failure exists because no matching run was created.
What should you preserve before deleting the disposable repository?
At minimum run IDs/attempts, event/ref/SHA evidence, conclusions, runner observations, and the comparison note so the learning claim remains reviewable.
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.
- Events that trigger workflows — event-specific ref/SHA and trigger behavior.
- GitHub CLI workflow-run documentation — read-only run list/view/log commands for optional local evidence collection.
Rechecked on 2026-09-09. The mandatory workflow
uses only GitHub workflow syntax and shell/Python already
available on the pinned ubuntu-24.04 hosted image; it
invokes no external action and uses no secret.
workflow_dispatch availability and event-specific
SHA/ref semantics should always be verified against current GitHub
documentation when this lesson is regenerated.
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.