Review Apps, Dynamic Environments, Ephemeral Test Stacks, Route Maps, and Environment Cleanup: Configuration, Design Choices, and Tradeoffs
Choose per-MR versus shared previews, manual versus automatic cleanup, naming and DNS strategies, route maps, credential scope, and lifecycle controls using explicit trust, cost, and audit boundaries.
Learning objectives
- Choose per-MR full stacks versus shared previews based on isolation, feedback quality, contention, cost, and cleanup complexity.
- Choose manual stop, branch/MR lifecycle stop, and auto_stop_in as complementary controls rather than interchangeable guarantees.
- Choose dynamic DNS, provider-generated URLs, or a local simulation without allowing raw branch names to become unsafe infrastructure identifiers.
- Use route maps when source-to-preview navigation adds value and use generic environment URLs when one landing page is enough.
- Scope credentials to review environments and untrusted contexts narrowly while keeping production credentials inaccessible to ordinary review-app jobs.
1. Design starts with isolation, trust, and cleanup—not YAML aesthetics
The best review-app architecture depends on what reviewers need to observe and what the preview can safely access. A full per-MR stack maximizes isolation but costs more and multiplies cleanup work. A shared preview reduces resource count but introduces contention and can blur “which revision am I viewing?” The YAML is downstream of those operational choices.
For every option, define source identity, resource ownership,
credential scope, maximum lifetime, cleanup actor, cleanup evidence,
and what happens when the normal stop path fails. In production
evidence, record CI_PIPELINE_SOURCE and the exact
CI_COMMIT_SHA alongside MR/environment identity so
design decisions are tied to the pipeline and revision actually
evaluated.
3. Automatic versus manual stop: layer controls instead of choosing one
Manual stop is useful when a reviewer still needs a preview.
MR/branch lifecycle stop is useful when the development object
closes. auto_stop_in catches stale previews that nobody
explicitly closes. Provider TTLs or reconciliation jobs catch cases
where the CI stop job never reaches the provider. These controls
solve different failure modes.
A mature design uses at least two independent cleanup mechanisms for chargeable or security-sensitive resources: an event-driven stop path and a time-based/reconciliation backstop. Neither should use broad deletion criteria.
4. Naming: environment identity, URL slug, and provider ID may differ
Keep three names explicit:
-
Environment:
review/mr-101for human GitLab history. -
GitLab slug: observed
CI_ENVIRONMENT_SLUG, limited by GitLab rules and useful for URL metadata. -
Provider ID: a provider-validated identifier such
as
rvw-101-a1b2c3, recorded in a manifest/tag set.
Do not derive destructive selectors from URLs. Do not use only a truncated slug as proof of uniqueness. Include immutable labels/tags such as project ID, MR IID, and source SHA where the provider supports them.
5. Dynamic DNS versus local/provider-generated URLs
| Approach | When it fits | Security/operations note |
|---|---|---|
Wildcard DNS *.review.example.test |
Many short-lived previews behind one ingress/load balancer. | Certificate, routing, tenant isolation, and hostname collision become platform concerns. |
| Provider-generated URL | Managed preview platforms or PaaS. | Record returned URL and resource ID; do not infer identity from URL alone. |
Local simulation / .invalid |
Training, CI logic, contract tests. | No external reachability; proves lifecycle logic safely. |
| Fixed shared URL | One serialized shared preview. | Must expose current SHA/owner visibly to avoid reviewer confusion. |
6. Route maps versus one generic URL
Use route maps when changed files have a stable public-page correspondence—documentation, static sites, page-based web apps. Keep mappings small and ordered from specific to general because the first source match wins. Test regexes against representative paths; an over-broad early rule can shadow later mappings.
A generic URL is often better for APIs, stateful workflows, SPAs where deep-link mapping is unreliable, or previews that require login/setup. Route maps are a developer-experience enhancement, not a correctness gate.
7. Credential model: review/* is a separate trust domain
Review jobs should receive only what the preview needs. For test
services, use synthetic accounts and short-lived scoped identity.
For project variables, environment scope review/* can
narrow availability, but do not use environment-scoped values to
control rules or includes during pipeline compilation.
Protected production credentials should remain unavailable to
ordinary unprotected review refs.
For fork MRs, treat code as untrusted. Do not bypass parent-project protections merely to make a review app convenient. If a trusted maintainer deliberately runs a parent-context pipeline, review exactly which variables/runners become available and make the deployment target disposable.
8. Cost and capacity: ephemeral resources need budgets and cardinality bounds
Cost is a correctness constraint when unbounded review creation can exhaust quotas and prevent production work. Define maximum concurrent reviews, resource class, TTL, idle behavior, database/storage policy, and stale-resource reconciliation. Measure queue time, creation latency, active count, age distribution, failed-cleanup count, and spend by review owner—not only pipeline duration.
Do not solve cost leaks with a nightly “delete everything matching review-*” script. Reconcile manifests/tags against open MRs and exact ownership labels, then delete only resources that are provably stale and inside the dedicated review boundary.
9. Environment actions and lifecycle semantics
| Action/control | GitLab meaning | Design implication |
|---|---|---|
start |
Default deployment action; creates/updates deployment record. | Use for actual review deployment. |
stop |
Marks stop lifecycle via stop job. | Teardown must remove external resource and verify. |
prepare / access /
verify
|
Lifecycle work that can interact with environment without ordinary new deployment intent. | Useful for prep/verification; understand auto-stop reset behavior. |
auto_stop_in |
Schedules environment stop after a duration. | Backstop; not exact provider TTL. |
| Stale cleanup | Maintainer/Owner operation to stop old non-protected environments. | Administrative reconciliation, not a substitute for per-resource teardown. |
10. Decision table: choose an architecture from evidence requirements
| Scenario | Recommended pattern | Prerequisites / trust | Evidence to require |
|---|---|---|---|
| Docs site; many MRs | Per-MR static review + route map + 1–2 day auto-stop. | Disposable hosting; no production secrets. | MR/SHA, env/slug, URL, mapped paths, cleanup proof. |
| Stateful app; expensive DB | Per-MR app + shared read-only test data or isolated lightweight schema. | Strong tenant isolation; bounded DB role. | Resource IDs, schema owner, TTL, DB cleanup check. |
| Small team; expensive stack | Serialized shared preview. | resource_group + visible current SHA. | Queue, owner, current SHA, previous handoff. |
| Untrusted forks | Build/test only or manual trusted deployment to disposable target. | No protected prod variables/runners. | Triggering user/context, variables policy, target isolation. |
| Training/course | Local guarded provider + fake URL metadata. | No credentials. | Manifest, hashes, bounded inventory, cleanup proof. |
11. Compatibility and offering notes
The mandatory review-app/environment path is Free-compatible. Protected environments and deployment approvals belong to later Chapter 21 and higher tiers; do not make them a hidden prerequisite here. Environment-scoped project variables are available in the project settings, while some group-level environment-scope capabilities are tiered—verify the exact scope/tier in your instance.
GitLab versions also evolve around environment actions and auto-stop behavior. This chapter records assumptions as of 2026-09-12; production platform code should include version tests or upgrade notes rather than silently depending on UI behavior.
12. Worked choice: 80 docs MRs, cheap static hosting, no secrets
Choice: per-MR static review environment named by
MR IID, wildcard or provider URL, route map for docs paths, manual
stop button plus auto_stop_in: 2 days, and a provider
reconciler keyed by project/MR labels.
Why: static previews are cheap, isolation improves review correctness, route maps reduce navigation friction, and no secret-bearing backend is needed. The bounded TTL/reconciler contains orphan risk. Evidence is MR IID + source SHA + environment name/slug + resource ID + content digest + stop job/operation ID + final absence query.
13. Design summary
- Choose isolation and cleanup guarantees before syntax.
- Use multiple cleanup layers for chargeable/security-sensitive resources.
- Keep environment name, GitLab slug, and provider resource ID distinct and recorded.
- Route maps optimize navigation, not deployment correctness.
- Review credentials belong to a low-trust, non-production boundary.
- Bound active-review cardinality, TTL, and resource class; reconcile exact ownership labels.
Knowledge check
When is a shared preview reasonable?
When resource cost is high and the team accepts serialized access, with strong evidence showing the currently deployed SHA/owner and resource_group-style coordination.
Why use more than one cleanup mechanism?
Event-driven stop paths can fail or never run; TTL/reconciliation provides an independent backstop for orphaned resources.
Should CI_ENVIRONMENT_SLUG be your universal cloud resource ID?
Not automatically. It has GitLab-specific truncation/suffix behavior and may collide with provider constraints; use provider-validated IDs and record the mapping.
What is the main value of route maps?
They improve reviewer navigation from changed source files to corresponding public review pages.
Why is broad stale-prefix deletion a bad cost-control strategy?
It can remove active/unrelated resources. Reconciliation should use exact ownership evidence and bounded scopes.
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.