Chapter 10Lesson 05~270 minutes

Checkpoint Lab — GitLab CI/CD Foundations: .gitlab-ci.yml, Pipelines, Jobs, Stages, and Execution Model

Build the smallest end-to-end pipeline, predict its source/SHA/stage/runner state, inject one safe failure, repair it from evidence, verify no secret or external side effect occurred, and clean up the disposable CI change.

CheckpointPipeline sourceCommit identityFailure repairVerificationChapter 11 bridge

Learning objectives

  • Predict pipeline source, commit SHA, stage ordering, job identity, and runner eligibility before execution.
  • Validate and run a tiny two-stage pipeline with one bounded artifact and no external write side effects.
  • Inject one deterministic command failure, diagnose it from pipeline/job evidence, and repair only the cause.
  • Verify independently that logs contain no credential value and that the evaluated checkout matches the intended commit.
  • Remove the synthetic CI configuration/branch only after preserving the evidence needed to explain the run.
Availability baseline (verified 2026-08-21). Core GitLab CI/CD, .gitlab-ci.yml, Pipeline Editor/CI Lint, jobs, stages, ordinary branch and merge-request pipelines, predefined variables, artifacts, runner scheduling, and CI_JOB_TOKEN are available on GitLab Free across GitLab.com, Self-Managed, and Dedicated. GitLab-hosted runners are provided for GitLab.com and GitLab Dedicated; Self-Managed installations provide and operate their own runner capacity. Hosted compute quotas, credits, machine types, and billing can change, so this chapter never hard-codes them and provides a validation-only path whenever a runner is unavailable. Current glab uses the glab ci command family; older pipe/pipeline aliases are deprecated.

1. Checkpoint mission

You are onboarding a repository to GitLab CI/CD. The production requirement is intentionally small: prove which commit ran, preserve a tiny evidence artifact, and demonstrate that the team can diagnose one failed job without weakening policy. Nothing may deploy, publish, push another repository, call a cloud account, or expose a token.

Use a disposable project or branch. The checkpoint is complete when another engineer can read your evidence and explain why the pipeline existed, what SHA it tested, what order the jobs followed, which runner executed them, why the injected failure occurred, and how you proved cleanup.

2. Preflight and assumptions

Item Requirement / fallback
Tier/offering GitLab Free; GitLab.com, Self-Managed, or Dedicated.
Role Developer or equivalent ability to push the disposable branch and run pipeline; no admin role required.
Runner One eligible runner if live execution is possible. Otherwise use CI Lint simulation + expected-state fixture.
Credentials Existing authenticated Git/glab session only. Do not create a PAT, trigger token, or runner token for this checkpoint.
Data Synthetic text only. No production secrets or customer data.
Cleanup Delete/revert only the lab branch/config after preserving SHA, pipeline/job IDs, and safe log evidence.

3. Prediction ledger — write this before pushing

Assume the lab branch is ch10/checkpoint and the push commit SHA is S. Record your predictions first:

  1. Pipeline source: a direct Git push should create a push pipeline unless creation rules say otherwise.
  2. Commit identity: CI_COMMIT_SHA should equal S and git rev-parse HEAD inside the job.
  3. Order: capture in stage inspect must succeed before verify in stage verify becomes eligible.
  4. Runner: an untagged job can run only if an available runner is eligible for untagged work under project policy.
  5. Artifact: only checkpoint-evidence.txt should be uploaded; no secret variable value should be included.

4. Baseline pipeline

Create .gitlab-ci.yml with two jobs. The second job verifies the checkout and artifact content but makes no external change.

stages:
  - inspect
  - verify

capture:
  stage: inspect
  script:
    - printf 'source=%s\n' "$CI_PIPELINE_SOURCE" | tee checkpoint-evidence.txt
    - printf 'sha=%s\n' "$CI_COMMIT_SHA" | tee -a checkpoint-evidence.txt
    - printf 'job=%s\n' "$CI_JOB_ID" | tee -a checkpoint-evidence.txt
    - printf 'runner=%s\n' "${CI_RUNNER_ID:-unknown}" | tee -a checkpoint-evidence.txt
    - test -n "${CI_JOB_TOKEN:-}"
    - printf 'job_token=present-not-printed\n' | tee -a checkpoint-evidence.txt
  artifacts:
    paths:
      - checkpoint-evidence.txt
    expire_in: 1 day

verify:
  stage: verify
  script:
    - test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
    - printf 'verified_sha=%s\n' "$CI_COMMIT_SHA"
    - printf 'external_side_effects=none\n' 

5. Validate, commit, and identify the exact pipeline

Validate in Pipeline Editor/CI Lint first. Then commit on the disposable branch and record the SHA before push.

REPO="GROUP/ch10-ci-foundations"
LAB_BRANCH="ch10/checkpoint"
mkdir -p ch10-checkpoint-evidence

git switch -c "$LAB_BRANCH"
git add -- .gitlab-ci.yml
git diff --cached --check
git diff --cached
git commit -m "ch10: checkpoint pipeline"
S="$(git rev-parse HEAD)"
printf '%s\n' "$S" | tee ch10-checkpoint-evidence/expected-sha.txt
git push -u origin "$LAB_BRANCH"

glab ci list -R "$REPO" --ref "$LAB_BRANCH" --output json --per-page 10   --jq '.[] | {id,status,source,ref,sha,web_url}'   | tee ch10-checkpoint-evidence/pipelines-baseline.json

6. Verify the baseline independently

Do not accept “green” alone. Check all of the following:

  • The newest intended pipeline has source=push, ref=ch10/checkpoint, and sha=S.
  • capture completed before verify became eligible under stage ordering.
  • The log shows runner metadata and job_token=present-not-printed, never the token itself.
  • The verify job proves git rev-parse HEAD == CI_COMMIT_SHA.
  • The artifact contains only the synthetic fields you predicted.
glab ci status -R "$REPO" --branch "$LAB_BRANCH" --compact
glab ci get -R "$REPO" --branch "$LAB_BRANCH" --output json --with-job-details   | tee ch10-checkpoint-evidence/pipeline-detail.json

git fetch origin "$LAB_BRANCH"
printf 'expected=%s remote=%s\n' "$S" "$(git rev-parse FETCH_HEAD)"

7. Inject one safe failure without changing permissions

Now modify only the verify job. Add a deterministic failing assertion after the correct SHA check:

verify:
  stage: verify
  script:
    - test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
    - echo "intentional checkpoint failure follows"
    - test "expected" = "intentionally-different"
Predict before push: pipeline creation should still succeed, capture should succeed, verify should run on an eligible runner and fail with a non-zero exit status at the final assertion, and the pipeline should become failed. Do not add allow_failure; that would hide rather than diagnose the cause.

8. Preserve the failure, identify the causal line, then repair it

Commit/push the failure and capture the new SHA F. Inspect the failed job log before changing anything. The original cause must remain visible in evidence.

git add -- .gitlab-ci.yml
git commit -m "ch10: inject deterministic checkpoint failure"
F="$(git rev-parse HEAD)"
printf '%s\n' "$F" | tee ch10-checkpoint-evidence/failing-sha.txt
git push

glab ci list -R "$REPO" --ref "$LAB_BRANCH" --status failed --output json --per-page 5   --jq '.[] | {id,status,source,ref,sha}'   | tee ch10-checkpoint-evidence/failed-pipelines.json

# Inspect the failed job in UI or glab ci get/trace.
glab ci get -R "$REPO" --branch "$LAB_BRANCH" --with-job-details
Diagnosis: configuration and pipeline creation succeeded, a runner executed verify, the checkout identity assertion passed, and the deliberately false string comparison returned non-zero. The causal layer is the job script. Runner tags, token scope, project permissions, and workflow rules do not need modification.
# Repair only the deliberately false assertion.
# Restore the verify job to:
verify:
  stage: verify
  script:
    - test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
    - printf 'verified_sha=%s\n' "$CI_COMMIT_SHA"
    - printf 'external_side_effects=none\n' 

9. Prove the repair is a new commit and new pipeline

Commit the repair, record SHA R, push, and confirm the successful pipeline is tied to R. Do not overwrite or delete the failed pipeline just to make history look clean—the failed run is useful evidence that the diagnostic process worked.

git add -- .gitlab-ci.yml
git commit -m "ch10: repair checkpoint assertion"
R="$(git rev-parse HEAD)"
printf '%s\n' "$R" | tee ch10-checkpoint-evidence/repaired-sha.txt
git push

glab ci status -R "$REPO" --branch "$LAB_BRANCH" --wait
glab ci list -R "$REPO" --ref "$LAB_BRANCH" --output json --per-page 10   --jq '.[] | {id,status,source,ref,sha}'   | tee ch10-checkpoint-evidence/pipelines-final.json

10. Security and side-effect verification

Search only your saved evidence and synthetic artifact for forbidden content. You are checking that the job printed a fixed presence message rather than a token value.

# Local evidence scan; patterns are intentionally conservative.
if grep -RniE 'glpat-|PRIVATE-TOKEN:|Authorization: Bearer|CI_JOB_TOKEN=' ch10-checkpoint-evidence; then
  echo "Review the matching line before sharing evidence."
else
  echo "No obvious credential-shaped output found in saved evidence."
fi

# Review the repository diff: only .gitlab-ci.yml should be synthetic.
git show --stat --oneline "$R"
git diff "${S}^".."$R" -- .gitlab-ci.yml
The string job_token=present-not-printed is safe synthetic text. A real token value, runner authentication token, PAT, secret variable, customer hostname, or production endpoint is not. If a real credential leaked, revoke/rotate it first; do not rely on deleting the log alone.

11. Cleanup/rollback

Only after evidence capture, remove the lab branch or revert the synthetic CI file. Verify the remote pointer before deletion so you do not target the wrong branch.

git fetch origin --prune
REMOTE_LAB_SHA="$(git rev-parse "origin/$LAB_BRANCH")"
printf 'remote_lab_sha=%s\n' "$REMOTE_LAB_SHA"
git log --oneline --decorate -n 5 "origin/$LAB_BRANCH"

# If this branch is confirmed disposable:
git push origin --delete "$LAB_BRANCH"
git fetch origin --prune

# Prove the remote branch is gone; no output is expected.
git ls-remote --heads origin "$LAB_BRANCH"
Do not delete the project unless the entire project was created solely for this checkpoint and you have verified there is no valuable issue, MR, registry, package, variable, runner assignment, or repository content to preserve.

12. Checkpoint verification checklist

  • ☐ CI Lint/Editor accepted the baseline configuration before commit.
  • ☐ Pipeline source/ref/SHA matched the prediction.
  • ☐ Stage order matched inspect → verify.
  • ☐ Runner identity was recorded without runner credential exposure.
  • ☐ CI_JOB_TOKEN presence was verified without printing its value.
  • ☐ The baseline artifact contained only synthetic metadata.
  • ☐ The deliberate failure preserved a failed pipeline/job and causal log line.
  • ☐ Repair changed only the failing assertion and produced a new successful pipeline on a new SHA.
  • ☐ No external system was changed.
  • ☐ The lab ref/configuration was removed or intentionally retained with documented purpose.

13. What Chapter 10 adds to the production operating model

You can now reason about GitLab CI/CD as a chain of inspectable resources rather than a YAML-triggered black box. The operating model has gained: versioned CI configuration, creation-time source/ref/SHA identity, explicit stage/job state, runner/executor trust boundaries, short-lived job identity, log/artifact evidence, and a failure taxonomy that points to the correct subsystem.

Chapter 11 moves inside the job definition itself: scripts, images, services, before_script, after_script, defaults, shell behavior, and image provenance. The mental model from this chapter is the prerequisite—you must know when and where a job executes before optimizing what it executes.

Knowledge check

The checkpoint’s failed pipeline was created and capture succeeded, but verify failed on a false test. Which layer owns the repair?

Why preserve F, the failing commit SHA, instead of only the repaired SHA R?

What two independent observations prove commit identity?

Why is allow_failure an incorrect repair for the injected assertion?

If no runner capacity is available, can the checkpoint still teach the execution model?

What is the next conceptual layer after this chapter?

Summary

The checkpoint established a repeatable CI operating discipline: predict → validate → bind to SHA → run/simulate → inspect pipeline/job/runner evidence → inject a bounded failure → preserve the cause → repair only the cause → verify security/side effects → clean up. That discipline scales to the more advanced GitLab CI/CD mechanisms that follow.

Official references

Chapter 11

CI/CD YAML, scripts, images, services, and lifecycle

Next you will move from pipeline orchestration into the job runtime itself: configuration inheritance, images/services, before_script/script/after_script behavior, shell assumptions, exit codes, and reproducibility.

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.