GitLab review apps: a fresh environment per merge request
Spin up an ephemeral deploy for every merge request and tear it down on merge — review real changes, not screenshots.
Reviewers staring at a diff are guessing whether the button actually works. A review app is a full running deploy of the merge request branch — created when the MR opens, linked on the MR page, destroyed when it merges or closes. GitLab builds this from two pipeline jobs, a dynamic environment, and whatever deploy tool you already use (Helm, kubectl, Terraform). The security win is the same as the UX win: people test the real change instead of approving from imagination.
You will wire deploy and stop jobs with environment: blocks, generate per-MR hostnames, cap resource sprawl with quotas and TTL sweepers, and keep preview namespaces isolated from production credentials. For protected environments, scanning gates, and runner isolation on the same platform, see Secure CI/CD with GitLab.
The stop job must run on merge or close — abandoned MRs otherwise leave environments billing indefinitely.
Deploy on merge request
Use $CI_MERGE_REQUEST_IID or $CI_COMMIT_REF_SLUG in release names so every MR gets a unique deployment. Point environment.url at the hostname reviewers will click. Restrict the job to merge request pipelines with rules: so main-branch deploys stay on your production track.
review:stage: deployscript:- helm upgrade --install mr-$CI_MERGE_REQUEST_IID ./chart \--namespace review-$CI_MERGE_REQUEST_IID --create-namespace \--set host=$CI_COMMIT_REF_SLUG.review.acme.devenvironment:name: review/$CI_COMMIT_REF_SLUGurl: https://$CI_COMMIT_REF_SLUG.review.acme.devon_stop: stop_reviewrules:- if: $CI_MERGE_REQUEST_IID
Tear it down automatically
The stop job references the same environment.name with action: stop. GitLab triggers it when the MR merges or closes if you wire on_stop correctly. Make stop automatic on merge; manual-only stop is how orphaned preview stacks accumulate. Add a weekly cleanup job that lists environments older than seven days and runs the stop action — belt and suspenders for draft MRs nobody merged.
stop_review:stage: deployscript:- helm uninstall mr-$CI_MERGE_REQUEST_IID -n review-$CI_MERGE_REQUEST_IID- kubectl delete namespace review-$CI_MERGE_REQUEST_IID --ignore-not-foundenvironment:name: review/$CI_COMMIT_REF_SLUGaction: stoprules:- if: $CI_MERGE_REQUEST_IIDwhen: on_success
curl -sI https://feat-oauth.review.acme.dev | head -1HTTP/2 200kubectl get ns | grep review-review-847 Active 2dafter merge, namespace should disappear within minutesIsolate previews from production
Review apps should use sandbox databases, fake payment keys, and network policies that block production CIDRs. Never mount production kubeconfig or cloud roles into MR pipelines from forks — use rules: to skip deploy jobs on external contributions or run them only after maintainer approval. Preview URLs are often guessable; do not expose admin interfaces without auth. Log review-environment access separately so you can spot scraping or credential stuffing against ephemeral hosts before they become a pattern.
Where this goes next
Review apps prove the change works; progressive delivery proves it works under traffic. Once previews are routine, add blue-green or canary promotion from the same pipeline and gate production deploys on scan results. Secure CI/CD with GitLab covers environments, protected branches, runner hardening, and supply-chain gates together.
Go deeper in a courseSecure CI/CD with GitLabEnvironments, review apps, protected deploys, scanning, and runner isolation.View course