Chapter 21Lesson 05~225 minutes

Checkpoint Lab — Protected Environments, Deployment Approvals, Freeze Windows, Manual Gates, and Separation of Duties

Stage a simulated production deployment through approval and freeze controls, prove authorization and health separately, exercise a documented emergency exception, and package auditable evidence.

Checkpoint labApproval recordFreeze policyEmergency pathEvidence packet

Learning objectives

  • Stage a simulated production deployment using exact candidate identity, an independent approval, an allowed deployer, and freeze-aware policy.
  • Predict at least two state changes before execution and verify each independently afterward.
  • Demonstrate a normal blocked/approved path and a documented emergency exception without broadening privileges.
  • Prove authorization and post-deploy health as separate evidence claims.
  • Produce a concise evidence packet and clean up only the disposable target.

1. Checkpoint scenario and success criteria

You are promoting a synthetic candidate to a local “production” directory. The builder is builder-carol, the approver is approver-alice, and the deployer is deployer-bob. A freeze may be active. Your result is accepted only if the exact candidate digest, authorization record, deploy actor, simulated deployment record, and independent health check agree.

No real production systems, credentials, cloud accounts, or protected-environment settings are changed in this mandatory path.

2. Preflight and current assumptions

Check Expected
Working directory Disposable repository you own
GitLab tier Free is sufficient for mandatory path; Premium/Ultimate controls are simulated
Tools POSIX shell, sha256sum, awk, grep
Runner Optional; local execution is a faithful simulation
Target .lab/ch21-checkpoint/target-production only
Verification date 2026-09-12

If you also run the YAML on GitLab, record the GitLab/Runner versions, executor, pipeline ID, job IDs, CI_PIPELINE_SOURCE, ref, and CI_COMMIT_SHA.

3. Predict before changing state

  1. Prediction A: an ineligible actor attempting deployment will be denied and the target directory will remain absent.
  2. Prediction B: after independent approval plus allowed deployer execution, the target digest will equal the candidate digest.
  3. Prediction C: when the freeze flag is active, the normal path will not proceed unless a separate exception record exists.

Write these predictions into your evidence packet before running the commands. The point is to compare expected and observed state, not merely to obtain a green result.

4. Build the candidate and policy files

set -eu
root=.lab/ch21-checkpoint
mkdir -p "$root"/{candidate,evidence,policy}
printf '%s
' 'version=21.checkpoint' 'payload=synthetic-only' > "$root/candidate/app.txt"
sha256sum "$root/candidate/app.txt" | tee "$root/evidence/candidate.sha256"
cat > "$root/policy/authorization.env" <<'EOF'
ALLOWED_DEPLOYER=deployer-bob
REQUIRED_APPROVER=approver-alice
PIPELINE_TRIGGERER=builder-carol
SELF_APPROVAL_ALLOWED=false
EOF
printf '%s
' 'prediction_A=deny_unlisted_actor' 'prediction_B=digest_matches_after_authorized_deploy'   'prediction_C=freeze_requires_exception' > "$root/evidence/predictions.env"

5. Implement the synthetic authorization contract

cat > .lab/ch21-checkpoint/authorize.sh <<'EOF'
#!/usr/bin/env sh
set -eu
root=.lab/ch21-checkpoint
. "$root/policy/authorization.env"
actor="${1:?actor}"
action="${2:?action}"
case "$action" in
  approve)
    [ "$actor" = "$REQUIRED_APPROVER" ] || { echo "DENY_APPROVER $actor"; exit 41; }
    [ "$SELF_APPROVAL_ALLOWED" = true ] || [ "$actor" != "$PIPELINE_TRIGGERER" ] || exit 42
    printf 'approval=APPROVED
approved_by=%s
' "$actor" > "$root/evidence/approval.env" ;;
  deploy)
    [ "$actor" = "$ALLOWED_DEPLOYER" ] || { echo "DENY_DEPLOYER $actor"; exit 43; }
    test -f "$root/evidence/approval.env" || { echo "DENY_NO_APPROVAL"; exit 44; } ;;
  *) exit 64 ;;
esac
EOF
chmod +x .lab/ch21-checkpoint/authorize.sh

6. Prove the denied actor path first

root=.lab/ch21-checkpoint
rm -rf "$root/target-production"
set +e
"$root/authorize.sh" intruder-mallory deploy > "$root/evidence/denial.log" 2>&1
rc=$?
set -e
test "$rc" -eq 43
test ! -e "$root/target-production"
printf 'denial_exit=%s
target_absent=true
' "$rc" >> "$root/evidence/denial.log"
cat "$root/evidence/denial.log"

This independently verifies Prediction A. Do not discard the denial log after the happy path succeeds.

7. Exercise freeze policy before approval

root=.lab/ch21-checkpoint
export CI_DEPLOY_FREEZE=true
if [ "${CI_DEPLOY_FREEZE:-}" = true ]; then
  printf '%s
' 'freeze=true' 'normal_path=BLOCKED' > "$root/evidence/freeze.env"
fi
cat "$root/evidence/freeze.env"

For real GitLab use, configure a deploy freeze so the pre-pipeline variable is supplied by GitLab, then implement a rule that blocks/changes the deployment path. Here the variable is simulated only to exercise decision logic.

8. Independent approval and emergency exception record

root=.lab/ch21-checkpoint
"$root/authorize.sh" builder-carol approve || test $? -eq 41
"$root/authorize.sh" approver-alice approve
cat >> "$root/evidence/freeze.env" <<'EOF'
exception_id=LAB-CH21-EMERGENCY-001
exception_authorized_by=approver-alice
exception_reason=checkpoint controlled recovery
EOF
cat "$root/evidence/approval.env"
cat "$root/evidence/freeze.env"

The exception does not erase the freeze. It adds a bounded, attributable decision alongside it. This is the governance pattern the lab is testing.

9. Deploy only the approved, verified candidate

root=.lab/ch21-checkpoint
"$root/authorize.sh" deployer-bob deploy
candidate_digest=$(awk '{print $1}' "$root/evidence/candidate.sha256")
current_digest=$(sha256sum "$root/candidate/app.txt" | awk '{print $1}')
test "$candidate_digest" = "$current_digest"
mkdir -p "$root/target-production"
cp "$root/candidate/app.txt" "$root/target-production/app.txt"
printf 'deployment_id=SIM-21-001
deployed_by=deployer-bob
candidate_digest=%s
'   "$candidate_digest" > "$root/evidence/deployment.env"
cat "$root/evidence/deployment.env"

10. Prove external state independently

root=.lab/ch21-checkpoint
expected=$(awk -F= '/candidate_digest/{print $2}' "$root/evidence/deployment.env")
actual=$(sha256sum "$root/target-production/app.txt" | awk '{print $1}')
test "$expected" = "$actual"
grep -q '^version=21.checkpoint$' "$root/target-production/app.txt"
printf 'health=PASS
target_digest=%s
verified_by=checkpoint-verifier
'   "$actual" > "$root/evidence/health.env"
cat "$root/evidence/health.env"

This independently verifies Prediction B. The target evidence is separate from the approval file and deployment receipt.

11. Equivalent GitLab Free control skeleton

stages: [verify, authorize, deploy, verify_target]

verify_candidate:
  stage: verify
  script: sha256sum dist/app.tar.gz
  artifacts:
    paths: [dist/app.tar.gz, dist/app.tar.gz.sha256]

production_authorization:
  stage: authorize
  needs: [verify_candidate]
  rules:
    - if: '$CI_DEPLOY_FREEZE'
      when: manual
      allow_failure: false
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
      allow_failure: false
    - when: never
  manual_confirmation: "Authorize this exact candidate for simulated production?"

deploy_production:
  stage: deploy
  needs: [verify_candidate, production_authorization]
  resource_group: production
  environment:
    name: production
  script: ./ci/deploy-verified.sh

verify_production:
  stage: verify_target
  needs: [deploy_production]
  script: ./ci/check-production-health.sh

On Premium/Ultimate, add protected-environment Allowed to deploy and deployment-approval rules rather than pretending this manual job is equivalent to those features.

12. Required evidence packet

Evidence Required content
Source/pipeline Pipeline source/ref/SHA or explicit local-simulation note
Candidate SHA-256 digest and candidate path
Authorization Allowed deployer/approver policy, denial log, approval record
Freeze Freeze state plus exception ID/actor/reason if exercised
Execution Job/deployment IDs or simulated deployment ID and deployer
Runner/tooling Runner/executor/version when used; local tool assumptions otherwise
Target Independent target digest/content and health result
Limitations Paid features simulated; no production credentials/resources used

If you use real Premium/Ultimate controls, add protected-environment configuration and deployment approval summary from the UI/API.

13. Cleanup/rollback

root=.lab/ch21-checkpoint
case "$root" in .lab/ch21-checkpoint) : ;; *) exit 90 ;; esac
rm -rf "$root/target-production"
test ! -e "$root/target-production"
# Review/copy the evidence packet before removing the rest.
# rm -rf "$root"

For a real deployment, rollback must restore a known candidate/artifact and be authorized under the same or an explicitly documented emergency policy. Never “rollback” by rebuilding source and assuming identical bytes.

14. What this chapter adds to a production operating model

You now have four separate claims for a production change: candidate identity, authorization, deployment execution, and external health. Freeze and exception policy adds governance context around those claims. This separation is what makes approval evidence meaningful rather than ceremonial.

Chapter 22 moves from authorization to Tags, Releases, Release CLI, Changelogs, Evidence, Asset Links, and Release-Orchestration Pipelines: version/tag identity, release objects, changelog/evidence, and asset links must preserve the same candidate provenance you just learned to authorize.

Knowledge check

What does the denied-actor test prove?

Why does the checkpoint keep the freeze record when exercising an emergency exception?

Which two files prove authorization and target health separately?

Why verify the digest again immediately before copying to the target?

What should change when moving this checkpoint to Premium/Ultimate?

Next lesson

Tags, Releases, Release CLI, Changelogs, Evidence, Asset Links, and Release-Orchestration Pipelines

Create release identity and evidence that preserve the exact authorized artifact lineage rather than rebuilding or retagging ambiguously.

Version and compatibility note

GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.

Official references and version notes

Documentation verification date: 2026-09-12. The mandatory learning path uses only disposable/local simulation plus GitLab Free features. Protected environments and deployment approvals are treated as Premium/Ultimate and are simulated unless the learner already has an authorized eligible project.

Current assumptions used in this chapter: Version-sensitive YAML, Runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations must be verified against the exact GitLab, GitLab Runner, tool, and external-system versions used in production. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for reproducible work.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.