Chapter 20Lesson 05~220 minutes

Checkpoint Lab — Review Apps, Dynamic Environments, Ephemeral Test Stacks, Route Maps, and Environment Cleanup

Provision two isolated synthetic review environments, prove unique identity and routes, deliberately orphan one bounded resource, repair only that resource, and produce lifecycle/cleanup evidence.

Checkpoint labTwo reviewsUnique identityOrphan repairEvidence

Learning objectives

  • Predict identity, environment, URL, resource, and stop-state changes for two independent review environments before provisioning them.
  • Provision two synthetic review resources with unique deterministic identifiers and independent evidence manifests.
  • Verify that each review maps to the intended source identity and that route-map output resolves to the intended preview path.
  • Create one bounded orphan condition, diagnose it from preserved evidence, and repair only the orphaned resource.
  • Produce an evidence packet that proves both successful cleanup and the absence of broad destructive operations.

1. Checkpoint: two reviews, one deliberate orphan, zero broad deletion

You will create two synthetic review resources, mr-101 and mr-102, each tied to a distinct source label. You will prove the resources are isolated, verify route-map/public-path expectations, clean up mr-101, deliberately leave mr-102 orphaned, diagnose it from preserved evidence, and then remove only mr-102.

The exercise uses local filesystem resources plus the GitLab YAML/environment contract. It is intentionally provider-neutral and Free-compatible. No real credential, DNS zone, cloud subscription, or production service is involved.

2. Assumptions and preflight

  • Documentation assumptions verified 2026-09-12.
  • Use a disposable project/branch or local checkout; never point REVIEW_ROOT at a shared/system directory.
  • Use the ci/reviewctl.sh guarded adapter from Lesson 2.
  • Use fake source labels sha-review-101 and sha-review-102 for local simulation; in GitLab evidence use real CI_COMMIT_SHA.
  • Use environment names review/mr-101 and review/mr-102; production deployment is out of scope.
  • If you also run the GitLab YAML, use MR pipelines and ordinary non-privileged runners.

3. Predict state changes before running anything

Prediction Before After provisioning After final cleanup
Resource inventory No mr-101/mr-102. Exactly two owned resources. Neither resource exists.
Identity No manifests. Each manifest contains its own review ID and source label. Evidence packet retains manifests/hashes even though targets are gone.
GitLab environments (if run) No review envs for checkpoint MRs. Two dynamic review environments, each with unique slug/URL metadata. Both stopped; external absence verified separately.
Route mapping Repository mapping only. Each resource has corresponding / and /docs/guide.html content. Route map remains versioned; resources are removed.
Orphan condition None. Intentionally created only for mr-102 after cleaning mr-101. Reconciled by exact resource ID.

4. Set up the bounded checkpoint sandbox

set -eu
export REVIEW_ROOT="$(pwd)/.sandbox/ch20-checkpoint"
case "$REVIEW_ROOT" in
  "$PWD"/.sandbox/ch20-checkpoint) ;;
  *) echo "unexpected review root" >&2; exit 70 ;;
esac
rm -rf -- "$REVIEW_ROOT"
mkdir -p "$REVIEW_ROOT" evidence/ch20
printf 'review_root=%s\n' "$REVIEW_ROOT" | tee evidence/ch20/preflight.txt

The only broad removal occurs after an exact equality guard against the known disposable checkpoint path. Production cleanup should prefer provider IDs/tags and API deletion rather than shell recursion.

5. Provision two unique review resources

./ci/reviewctl.sh deploy mr-101 sha-review-101 | tee evidence/ch20/deploy-101.txt
./ci/reviewctl.sh deploy mr-102 sha-review-102 | tee evidence/ch20/deploy-102.txt

./ci/reviewctl.sh inspect mr-101 | tee evidence/ch20/manifest-101.txt
./ci/reviewctl.sh inspect mr-102 | tee evidence/ch20/manifest-102.txt
find "$REVIEW_ROOT" -mindepth 1 -maxdepth 2 -print | sort | tee evidence/ch20/inventory-after-deploy.txt

Expected: two separate directories and two manifests. If either manifest names the wrong review ID/source label, stop; that is an identity failure and cleanup should not continue automatically.

6. Prove content and route-map targets independently

test -f "$REVIEW_ROOT/mr-101/site/index.html"
test -f "$REVIEW_ROOT/mr-101/site/docs/guide.html"
test -f "$REVIEW_ROOT/mr-102/site/index.html"
test -f "$REVIEW_ROOT/mr-102/site/docs/guide.html"

sha256sum   "$REVIEW_ROOT/mr-101/site/index.html"   "$REVIEW_ROOT/mr-102/site/index.html"   | tee evidence/ch20/content-digests.txt
printf '%s\n'   'demo-site/index.html -> /'   'demo-site/docs/guide.html -> /docs/guide.html'   | tee evidence/ch20/route-expectations.txt

This proves the synthetic external target contains the expected files. It does not claim that GitLab itself validated the route-map regex; in a real reachable review app, preserve the MR/file navigation evidence too.

7. Checkpoint GitLab configuration

review_deploy:
  stage: deploy
  resource_group: "review-mr-$CI_MERGE_REQUEST_IID"
  script:
    - printf 'resource_id=mr-%s\nsource_sha=%s\n' "$CI_MERGE_REQUEST_IID" "$CI_COMMIT_SHA" | tee review-manifest.txt
  artifacts:
    paths: [review-manifest.txt]
    expire_in: 7 days
  environment:
    name: "review/mr-$CI_MERGE_REQUEST_IID"
    url: "https://$CI_ENVIRONMENT_SLUG.review.example.invalid/"
    on_stop: review_stop
    auto_stop_in: 2 days
    deployment_tier: development
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

review_stop:
  stage: deploy
  resource_group: "review-mr-$CI_MERGE_REQUEST_IID"
  script:
    - printf 'cleanup_request=mr-%s\n' "$CI_MERGE_REQUEST_IID"
  environment:
    name: "review/mr-$CI_MERGE_REQUEST_IID"
    action: stop
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      when: manual
      allow_failure: true

If executed in GitLab, preserve pipeline/job IDs, CI_PIPELINE_SOURCE, CI_COMMIT_SHA, MR IID, environment IDs/names/slugs/URLs/states, and the artifact manifest. The local provider resources remain a separate simulation and must not be claimed as GitLab-managed infrastructure.

8. Clean mr-101 normally and prove it is gone

./ci/reviewctl.sh destroy mr-101 | tee evidence/ch20/cleanup-101.txt
if test -e "$REVIEW_ROOT/mr-101"; then
  echo 'mr-101 still exists' >&2
  exit 71
fi
find "$REVIEW_ROOT" -mindepth 1 -maxdepth 2 -print | sort | tee evidence/ch20/inventory-after-101.txt

Expected: mr-102 remains untouched. This is an important negative assertion: cleanup is correct only if the intended resource disappears and unrelated resources remain.

9. Deliberately create and diagnose one orphan condition

Do not call destroy for mr-102 yet. Pretend its GitLab stop metadata was marked or the MR closed, but the provider adapter was never invoked. Capture the inconsistency:

printf '%s\n' 'simulated_gitlab_state=stopped' > evidence/ch20/orphan-102.txt
./ci/reviewctl.sh inspect mr-102 | tee -a evidence/ch20/orphan-102.txt
find "$REVIEW_ROOT" -mindepth 1 -maxdepth 2 -print | sort | tee -a evidence/ch20/orphan-102.txt

Interpretation: GitLab lifecycle intent and external state disagree. The resource manifest proves the exact orphan identity and owner. You have enough evidence to repair only mr-102; there is no reason to delete the whole review root.

10. Repair only the orphan and verify absence

./ci/reviewctl.sh destroy mr-102 | tee evidence/ch20/cleanup-102.txt
if test -e "$REVIEW_ROOT/mr-102"; then
  echo 'mr-102 still exists' >&2
  exit 72
fi
find "$REVIEW_ROOT" -mindepth 1 -print | sort | tee evidence/ch20/final-inventory.txt
test ! -s evidence/ch20/final-inventory.txt
printf '%s\n' 'final_cleanup=verified' | tee evidence/ch20/final-status.txt

The repair is intentionally narrow. It demonstrates the production principle: reconcile by exact ownership/resource identity, not by a broad wildcard that could erase other review environments.

11. Evidence packet

Keep the following together for the checkpoint:

  • Assumptions and documentation verification date.
  • Pipeline source/ref/SHA and MR IID if run in GitLab.
  • Merged/effective configuration or CI Lint evidence for deploy/stop job inclusion.
  • Pipeline/job IDs and runner/executor/image versions if run in GitLab.
  • Environment ID/name/slug/URL/state and auto_stop_in/on_stop relationship.
  • manifest-101.txt, manifest-102.txt, and content digests.
  • Inventory after deploy, after first cleanup, orphan evidence, and final empty inventory.
  • Route-map file/source SHA and expected public-path mapping.
  • An assumptions/limitations note stating that .invalid URLs and filesystem resources simulate provider behavior.

12. Verification checklist

  • Two unique review resources were created and no third resource appeared.
  • Each manifest tied its exact resource ID to its intended source label/SHA.
  • Both review roots contained the expected site files and route-map target paths.
  • Cleanup of mr-101 did not modify mr-102.
  • The orphan condition was preserved before repair.
  • Repair targeted only mr-102.
  • The final bounded inventory is empty.
  • No production credentials, DNS, cloud account, Kubernetes cluster, or broad delete selector was used.

13. Final cleanup and rollback

After saving the evidence packet, remove only the disposable checkpoint root and generated local evidence if you no longer need them. Keep the course source changes/route-map only if they belong to your training branch. If a real GitLab environment was created, stop it and verify provider cleanup before deleting the disposable project.

There is no “rollback” that restores a deleted review environment automatically. If reviewers need it again, create a new review resource from the recorded source/artifact identity. Do not resurrect stale provider state whose ownership is uncertain.

14. What Chapter 20 adds—and the bridge to Chapter 21

Chapter 20 adds lifecycle ownership to the delivery model: a review environment has deterministic source identity, bounded resource identity, safe navigation metadata, low-trust credentials, explicit stop paths, a time-based backstop, and independent proof of deletion. This prevents ephemeral infrastructure from becoming invisible production-adjacent debt.

Chapter 21 moves from ephemeral review targets to deployment authorization: protected environments, approvals, freeze windows, manual gates, and separation of duties. The distinction remains crucial—authorization to deploy is not the same as deployment success or target health.

Knowledge check

What proves that the two checkpoint reviews are isolated?

Why intentionally leave mr-102 orphaned?

What negative assertion must cleanup of mr-101 prove?

Why is final empty provider inventory stronger than a successful stop job?

What control plane becomes central in Chapter 21?

Next chapter

Chapter 21 — Protected environments, approvals, freeze windows, manual gates, and separation of duties

Add an independent deployment-authorization control plane without confusing approval with successful deployment or external health.

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. Review-app, environment lifecycle, variable, protected-resource, route-map, and cleanup semantics are version-sensitive. Re-check the GitLab version used by your organization before copying exact lifecycle behavior into production.

  • Review apps — dynamic review environments, merge-request workflows, stop behavior, and route maps.
  • Environments — static/dynamic environments, environment states, on_stop, auto_stop_in, stale cleanup, deletion, and environment-scoped variables.
  • CI/CD YAML syntax reference — environment:name, url, on_stop, action, auto_stop_in, resource_group, and rules.
  • Predefined CI/CD variables — CI_COMMIT_REF_SLUG, CI_ENVIRONMENT_NAME, CI_ENVIRONMENT_SLUG, CI_ENVIRONMENT_URL, and merge-request variables.
  • CI/CD variables — protection, masking/hidden behavior, fork/MR exposure, and environment scope.
  • Environments API — environment metadata, stop, and stale-environment operations when API automation is appropriate.
  • Deployment safety — protected-resource and deployment-safety boundaries that also matter for review infrastructure.

Current assumptions used in this chapter: review apps and dynamic environments are available on Free, Premium, and Ultimate across GitLab.com, Self-Managed, and Dedicated. CI_COMMIT_REF_SLUG is normalized and shortened to 63 bytes; CI_ENVIRONMENT_SLUG is derived from environment:name, is truncated to 24 characters, and uppercase environment names can receive a random suffix. Route maps live in .gitlab/route-map.yml, are evaluated in declaration order, and the first matching source rule determines the public path; the merge-request widget can surface up to five mapped pages before filtering. environment:auto_stop_in accepts human-readable durations (and variables), but environment expiration is serviced by background work that runs approximately hourly, so it is not an exact timer. Deploy and stop jobs should have compatible rules/only/except; a stop job also has to be runnable when cleanup is needed. To trigger on_stop from the Environments UI, deploy and stop jobs must share a resource_group. Protected environments are Premium/Ultimate and are not required by the mandatory lab. The mandatory path uses fake .invalid URLs and guarded local filesystem resources under a temporary sandbox—no cloud account, Kubernetes cluster, production DNS, PAT, deploy token, or real secret is required.

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.