BlogCI/CD

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.

Nov 12, 2025·4 min readIntermediate·By the SecOpsLog team · command-tested

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.

Review app lifecycle on a merge request

The stop job must run on merge or close — abandoned MRs otherwise leave environments billing indefinitely.

1MR opensreview job triggers2Helm/kubectldeploy to preview NS3Environment URLlink on MR page4Reviewers testreal app, real API5New commitsredeploy same env6MR mergesstop_review runs7Namespace gonequota reclaimed

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.

.gitlab-ci.yml
review:
stage: deploy
script:
- 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.dev
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.acme.dev
on_stop: stop_review
rules:
- 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.

.gitlab-ci.yml
stop_review:
stage: deploy
script:
- helm uninstall mr-$CI_MERGE_REQUEST_IID -n review-$CI_MERGE_REQUEST_IID
- kubectl delete namespace review-$CI_MERGE_REQUEST_IID --ignore-not-found
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
rules:
- if: $CI_MERGE_REQUEST_IID
when: on_success
bash — confirm the environment existslive
curl -sI https://feat-oauth.review.acme.dev | head -1
HTTP/2 200
kubectl get ns | grep review-
review-847 Active 2d
after merge, namespace should disappear within minutes

Isolate 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.

Preview hygiene
Risky defaults
Shared prod database
Long-lived MR envs
No resource quotas
Fork MRs with deploy keys
Safer pattern
Ephemeral DB per review NS
Auto stop on merge/close
Quota + TTL sweeper cron
Protected deploy on forks
Cap the sprawl
Every open MR can mean a running cluster, load balancer, and database. Set namespace ResourceQuotas, limit concurrent review environments per project, and run a sweeper that deletes environments older than N days. Abandoned draft MRs otherwise become your largest cloud line item.

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

Related posts