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.
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_ROOTat a shared/system directory. -
Use the
ci/reviewctl.shguarded adapter from Lesson 2. -
Use fake source labels
sha-review-101andsha-review-102for local simulation; in GitLab evidence use realCI_COMMIT_SHA. -
Use environment names
review/mr-101andreview/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_stoprelationship. -
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
.invalidURLs 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-101did not modifymr-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?
Distinct resource IDs/manifests, distinct bounded directories, source labels, and inventory showing both resources independently before cleanup.
Why intentionally leave mr-102 orphaned?
To practice diagnosing divergence between GitLab lifecycle intent and external resource state while preserving evidence and avoiding broad cleanup.
What negative assertion must cleanup of mr-101 prove?
mr-101 is absent while mr-102 remains unchanged; safe cleanup should not affect unrelated reviews.
Why is final empty provider inventory stronger than a successful stop job?
It independently verifies the external resources are gone rather than trusting CI/GitLab lifecycle status alone.
What control plane becomes central in Chapter 21?
Deployment authorization: protected environments, approvers, freeze/manual gates, and separation of duties, independent of deployment/health state.
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, andrules. -
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.