Chapter 11Lesson 05~275 minutes

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.

CheckpointTwo jobsLifecycle orderFailure repairVerificationChapter 12 bridge

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.
Availability baseline (verified 2026-08-21). The CI/CD YAML keywords taught here—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:

  1. inspect_runtime inherits the default image, default before_script, and default after_script.
  2. verify_override inherits the default image but replaces before_script and disables after_script.
  3. The main script can see a variable exported by the inherited default before_script; after_script cannot.
  4. A file written in the checkout can still be read by after_script because filesystem state persists even though shell state does not.
  5. If the optional service job is used, it reaches the sidecar as web, not localhost, 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_runtime log shows markers 01 → 10 → 90 in lifecycle order.
  • ☐ Main script sees SESSION_MARKER; after script proves it is absent in the fresh shell.
  • ☐ after_script can read checkpoint-state.txt.
  • ☐ verify_override shows 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: []
Prediction: YAML validation and pipeline creation should still succeed. The job should start on an eligible runner and fail at the final 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
Pre-delete check: confirm the branch name and verify that all required pipeline/job/SHA/log evidence has been recorded. Branch deletion removes the convenient ref pointer even though commits may remain reachable through other GitLab metadata for some time.

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?

The job-specific before_script is present. What happens to default:before_script?

Why can after_script read checkpoint-state.txt but not SESSION_MARKER?

Why is allow_failure not an acceptable repair for the deliberate false assertion?

What if the learner only has a Shell executor?

What conceptual question does Chapter 12 add?

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

Chapter 12

rules, workflow, sources, changes, and dynamic creation

Next you will control when pipelines and jobs are created, distinguish push/MR/schedule/API sources, and debug configurations that are valid but intentionally materialize no pipeline or no job.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.