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.
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.
.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:
-
Pipeline source: a direct Git push should create
a
pushpipeline unless creation rules say otherwise. -
Commit identity:
CI_COMMIT_SHAshould equalSandgit rev-parse HEADinside the job. -
Order:
capturein stageinspectmust succeed beforeverifyin stageverifybecomes eligible. - Runner: an untagged job can run only if an available runner is eligible for untagged work under project policy.
-
Artifact: only
checkpoint-evidence.txtshould 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, andsha=S. -
capturecompleted beforeverifybecame eligible under stage ordering. -
The log shows runner metadata and
job_token=present-not-printed, never the token itself. -
The
verifyjob provesgit 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"
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
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
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"
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_TOKENpresence 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?
The job script/execution layer. Pipeline creation, runner eligibility, and permissions already succeeded.
Why preserve F, the failing commit SHA, instead of only the repaired SHA R?
It binds the failure evidence to the exact configuration that caused it and makes the diagnosis reproducible/auditable.
What two independent observations prove commit identity?
GitLab pipeline/job metadata records the SHA, and the running job verifies CI_COMMIT_SHA equals git rev-parse HEAD for its checkout.
Why is allow_failure an incorrect repair for the injected assertion?
It changes policy/status semantics while leaving the defect in place; the checkpoint requires fixing the causal command.
If no runner capacity is available, can the checkpoint still teach the execution model?
Yes. Use CI Lint/pipeline simulation plus the expected-state fixture and runner inspection; live compute is optional, not a purchase requirement.
What is the next conceptual layer after this chapter?
Chapter 11 focuses on job contents and runtime behavior—YAML job keys, scripts, images, services, defaults, before_script, after_script, shell semantics, and provenance.
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
- GitLab Docs — CI/CD
- GitLab Docs — Get started with GitLab CI/CD
- GitLab Docs — CI/CD YAML syntax reference
- GitLab Docs — Validate CI/CD configuration
- GitLab Docs — Pipeline editor
- GitLab Docs — Pipelines
- GitLab Docs — CI/CD jobs
- GitLab Docs — Job execution flow
- GitLab Docs — Predefined CI/CD variables
- GitLab Docs — CI/CD variables
- GitLab Docs — CI/CD job token
- GitLab Docs — Runners
- GitLab Docs — Configure runners
- GitLab Docs — GitLab-hosted runners
- GitLab Docs — Pipelines API
- GitLab Docs — Jobs API
- GitLab Docs — CI Lint API
- GitLab Docs — Job rules and CI_PIPELINE_SOURCE
- GitLab CLI — ci
- GitLab CLI — ci list
- GitLab CLI — ci get
- GitLab CLI — ci status
- GitLab CLI — runner list
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.