Checkpoint Lab — CI/CD YAML, Scripts, Images, Services, before_script, after_script, and Defaults
Build a two-job disposable pipeline, predict its exact lifecycle and runtime assumptions, inject one bounded script/service failure, repair the causal layer, pin or record dependencies, preserve evidence, and clean up safely.
Learning objectives
- Predict resolved defaults, lifecycle order, shell-state persistence, image/runtime identity, and service reachability before execution.
- Build a two-job Free-compatible pipeline with one explicit image and one optional harmless service sidecar.
- Inject one bounded failure caused by a command or readiness assumption and preserve the original evidence.
- Repair only the causal layer without allow_failure, blanket retries, privilege changes, or secret exposure.
- Pin or record runtime dependencies, prove no external side effect occurred, and remove all synthetic CI changes after evidence capture.
default,
image, services, before_script,
script, and after_script—are part of core
GitLab CI/CD and are available on Free, Premium, and Ultimate across
GitLab.com, Self-Managed, and Dedicated. Runtime behavior still
depends on the runner executor. In particular, container
image/services semantics require a
compatible container-capable executor; a Shell executor runs commands
directly on the runner host and has materially different isolation and
dependency assumptions. Hosted-compute quotas and registry
availability can change, so all mandatory learning also has a CI
Lint/log-fixture or local-container fallback.
1. Checkpoint mission
You are standardizing the first reusable runtime conventions for a small GitLab project. The policy must show exactly how defaults propagate, how job-specific overrides behave, how lifecycle hooks execute, which image/service dependency is used, and how a failure is diagnosed. The lab must not deploy, publish packages, mutate a registry, access production infrastructure, or expose credentials.
2. Preflight and required assumptions
| Item | Requirement / safe fallback |
|---|---|
| Tier/offering | GitLab Free on GitLab.com, Self-Managed, or Dedicated. |
| Role | Enough project access to push a disposable branch and run/inspect CI; no administrator role required. |
| Runner | Container-capable runner for live image/service path. Otherwise CI Lint + expected-state fixture; no paid purchase required. |
| Images | Explicit version tags for live beginner lab; record resolved digest if available. Do not invent a digest. |
| Secrets |
None required. Never print CI_JOB_TOKEN or
runner credentials.
|
| Cleanup | Remove the lab branch/YAML or revert the synthetic commit after recording pipeline/job/SHA/runtime evidence. |
3. Prediction ledger — write it before the first push
Before execution, record at least these predictions:
-
inspect_runtimeinherits the default image, defaultbefore_script, and defaultafter_script. -
verify_overrideinherits the default image but replacesbefore_scriptand disablesafter_script. -
The main
scriptcan see a variable exported by the inherited defaultbefore_script;after_scriptcannot. -
A file written in the checkout can still be read by
after_scriptbecause filesystem state persists even though shell state does not. -
If the optional service job is used, it reaches the sidecar as
web, notlocalhost, and must wait for readiness.
4. Baseline two-job pipeline
default:
image: alpine:3.22
before_script:
- echo "01 default before"
- export SESSION_MARKER="main-shell-only"
after_script:
- echo "90 default after"
- test -z "${SESSION_MARKER:-}"
- test -f checkpoint-state.txt
- cat checkpoint-state.txt
stages: [inspect, verify]
inspect_runtime:
stage: inspect
script:
- echo "10 inspect script"
- test "$SESSION_MARKER" = "main-shell-only"
- test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
- printf 'sha=%s
' "$CI_COMMIT_SHA" > checkpoint-state.txt
- printf 'image=%s
' "${CI_JOB_IMAGE:-not-reported}" >> checkpoint-state.txt
- printf 'side_effects=none
' >> checkpoint-state.txt
artifacts:
paths: [checkpoint-state.txt]
expire_in: 1 day
verify_override:
stage: verify
before_script:
- echo "02 override before"
script:
- echo "20 verify script"
- test -z "${SESSION_MARKER:-}"
- test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
after_script: []
5. Validate, commit, and bind the run to an exact SHA
Validate in Pipeline Editor/CI Lint before commit. Then use the disposable branch, inspect the staged diff, and record the SHA.
LAB_BRANCH="ch11/checkpoint"
mkdir -p ch11-checkpoint-evidence
git switch -c "$LAB_BRANCH"
git add -- .gitlab-ci.yml
git diff --cached --check
git diff --cached
git commit -m "ch11: checkpoint CI runtime semantics"
BASELINE_SHA="$(git rev-parse HEAD)"
printf '%s
' "$BASELINE_SHA" | tee ch11-checkpoint-evidence/baseline-sha.txt
git push -u origin "$LAB_BRANCH"
6. Verify the baseline independently
-
☐ Pipeline/job metadata SHA equals
BASELINE_SHA. -
☐
inspect_runtimelog shows markers 01 → 10 → 90 in lifecycle order. -
☐ Main script sees
SESSION_MARKER; after script proves it is absent in the fresh shell. -
☐
after_scriptcan readcheckpoint-state.txt. -
☐
verify_overrideshows marker 02 but not marker 01 or 90. - ☐ No credential value, production URL, package publication, or deployment occurs.
7. Optional live service extension (container-capable runner only)
Add this third job only if the runner supports services. Otherwise validate it as a fixture and explain why the executor boundary prevents live execution.
service_contract:
stage: verify
image: alpine:3.22
services:
- name: nginx:1.28-alpine
alias: web
before_script: []
script:
- |
n=1
while [ "$n" -le 10 ]; do
if wget -qO- http://web/ >/dev/null; then
echo "service_ready_attempt=$n"
exit 0
fi
n=$((n + 1))
sleep 1
done
echo "service readiness timeout" >&2
exit 1
after_script: []
8. Inject one bounded failure and predict the evidence
Break verify_override with a deterministic shell
assertion. Do not change runner tags, credentials, project settings,
or failure policy.
verify_override:
stage: verify
before_script:
- echo "02 override before"
script:
- echo "20 verify script"
- test -z "${SESSION_MARKER:-}"
- echo "intentional checkpoint failure follows"
- test "expected" = "different"
after_script: []
test with a non-zero exit status.
That is a script-layer failure, not a runner, image, permission, or
pipeline-creation failure.
9. Preserve the failed state before repair
Commit/push the broken configuration and record
FAILED_SHA. Save the failed pipeline ID/job ID and the
log line surrounding the false assertion. Do not erase the failure
by retrying with modified permissions or adding
allow_failure.
git add -- .gitlab-ci.yml
git commit -m "ch11: inject deterministic runtime failure"
FAILED_SHA="$(git rev-parse HEAD)"
printf '%s
' "$FAILED_SHA" | tee ch11-checkpoint-evidence/failed-sha.txt
git push
# In GitLab UI or current glab ci commands, record pipeline/job IDs and failed log evidence.
10. Repair only the causal layer
Remove the deliberately false comparison and restore the correct
assertions. Commit as REPAIRED_SHA, run again, and
compare the three states: baseline, failed, repaired.
| Evidence | Baseline | Failed | Repaired |
|---|---|---|---|
| Configuration SHA | Recorded | Recorded | Recorded |
| YAML validation | Valid | Valid | Valid |
| Runner eligibility | Eligible / simulated | Eligible / simulated | Eligible / simulated |
| verify job | Success | Fails at false assertion | Success |
| Permissions/secrets | Unchanged | Unchanged | Unchanged |
| External side effects | None | None | None |
11. Pin or record runtime dependencies
Before closing the checkpoint, record the image references used by the job. If the runner/registry exposes the resolved digest, preserve it in evidence. In a production pipeline, prefer a reviewed digest or internally promoted immutable reference where exact identity matters. Do not paste a made-up digest into the YAML.
If you used the optional NGINX service, record its image reference separately from the job image; they are independent supply-chain dependencies.
12. Cleanup and rollback
Cleanup must be scoped to the synthetic resources. Preserve evidence first, then remove the lab branch or revert the CI commit if the project must remain. Do not delete a shared project, change runner registration, or modify group policy as part of this checkpoint.
# After evidence capture and after confirming you are on a safe branch:
git switch main
git pull --ff-only
# Optional for a disposable lab branch:
git push origin --delete ch11/checkpoint
git branch -D ch11/checkpoint
13. Final verification checklist
- ☐ Exact curriculum title/path and disposable branch were used.
- ☐ Baseline, failed, and repaired commit SHAs are recorded.
- ☐ Resolved default/override behavior is proven by ordered log markers.
- ☐ Main-shell versus after-shell state is proven without printing secrets.
- ☐ Image/service references and available digest evidence are recorded.
- ☐ Optional service used an alias and bounded readiness check.
-
☐ Failure was repaired at the script/readiness layer without
allow_failure, privilege escalation, or policy bypass. - ☐ No external system was changed and synthetic resources were removed or intentionally retained with documentation.
14. What Chapter 11 adds to the production operating model
The GitLab operating model now extends from pipeline/job identity into the runtime contract itself: resolved defaults, executor capability, explicit image and service dependencies, shell/lifecycle boundaries, working-directory behavior, exit-code semantics, and dependency provenance. You can distinguish “configuration said this” from “runner executed this,” and you can prove the difference.
Chapter 12 adds the next control layer: rules,
workflow, pipeline sources, changes/conditions, and
dynamic pipeline creation. That chapter decides
whether a pipeline or job is created; this chapter
established what happens inside the job once it exists.
Knowledge check
Why does the checkpoint record three SHAs instead of only the repaired one?
They bind baseline, failure, and repair to exact versioned configurations so the diagnostic story is reproducible and auditable.
The job-specific before_script is present. What happens to default:before_script?
It is replaced for that job, not appended or deep-merged.
Why can after_script read checkpoint-state.txt but not SESSION_MARKER?
Filesystem state in the workspace persists, but after_script uses a fresh shell so prior shell exports are not preserved.
Why is allow_failure not an acceptable repair for the deliberate false assertion?
It changes status policy without fixing the causal defect; the checkpoint requires repairing the command itself.
What if the learner only has a Shell executor?
Complete CI Lint/merged-config reasoning and use the static/local service fixture; do not mutate shared runner infrastructure to force container semantics.
What conceptual question does Chapter 12 add?
It teaches creation-time control: which pipeline/job should exist for a given pipeline source, ref, change set, or condition.
Summary
The checkpoint proved a production-grade learning loop for CI runtime configuration: predict resolved YAML → validate → bind to SHA → observe lifecycle and executor evidence → inject a bounded failure → preserve the original cause → repair only the causal layer → record dependency identity → verify no secret/external side effect → clean up. That foundation is essential before adding conditional pipeline creation in Chapter 12.
Official references
- GitLab Docs — CI/CD YAML syntax reference
- GitLab Docs — Scripts and job logs
- GitLab Docs — Deprecated CI/CD keywords
- GitLab Docs — Use CI/CD configuration from other files
- GitLab Docs — Run CI/CD jobs in Docker containers
- GitLab Docs — Services
- GitLab Docs — Docker executor
- GitLab Docs — Shell executor
- GitLab Docs — Runner executors
- GitLab Docs — Pipeline editor
- GitLab Docs — CI Lint
- GitLab Docs — Predefined CI/CD variables
- GitLab Docs — Runner security
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.