In GitLab CI, how do you give every merge request its own review app environment, and where does the environment name come from?
answer
- variable inside the environment name
- one environment per branch
- branch name must be made DNS-safe
- View app button in the MR widget
- creating without stopping leaks forever
basics
~20 sPut a CI/CD variable in the environment name so each branch creates its own environment — typically name: review/$CI_COMMIT_REF_SLUG with a matching url. GitLab expands the variable per pipeline, so every merge request gets a separately tracked deployment and a View app link.
solid answer
~50 sA review app is just an ordinary deploy job whose `environment:name` contains a variable, so the name resolves differently on every branch. The idiomatic form is `name: review/$CI_COMMIT_REF_SLUG` with `url: https://$CI_COMMIT_REF_SLUG.review.example.com`. `CI_COMMIT_REF_SLUG` is the branch name lowercased, truncated, and with everything outside `a-z0-9` replaced by hyphens, which is what makes it safe in a hostname or a Kubernetes namespace — the raw `CI_COMMIT_REF_NAME` is not. GitLab calls a variable-named environment a **dynamic environment**: it is created on first deployment and tracked separately, and because the branch has an open merge request, GitLab shows a **View app** button in the MR widget so a reviewer or QA can click straight through. The `review/` prefix groups them into one folder in the UI. The part people forget is the other end: a dynamic environment that is never stopped leaks namespaces and DNS records forever, so `on_stop` and `auto_stop_in` belong in the same job.
code
yaml · 20 linesdeploy_review:
stage: deploy
script:
- ./deploy.sh "$CI_ENVIRONMENT_SLUG"
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop_review
auto_stop_in: 3 days
stop_review:
stage: deploy
variables:
GIT_STRATEGY: none
script:
- ./teardown.sh "$CI_ENVIRONMENT_SLUG"
when: manual
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stopgo deeper
Know that putting a variable such as $CI_COMMIT_REF_SLUG in environment:name gives each branch its own environment, and that GitLab then shows a View app link on the merge request.
Explain the mechanics: dynamic environment naming, why the slug is DNS-safe where the raw ref name is not, how the review/ prefix groups the UI, and that on_stop belongs in the same block.
Show you have run this in production — collision from slug truncation, per-branch database strategy, narrowly scoped deploy credentials, and gating creation so unattended branches do not consume the cluster.
Own the economics: how many concurrent review apps the platform can afford, whether they are automatic or on demand, what data they get, and what the fallback is when the environment is too heavy to reproduce per branch.
## The idea GitLab does not have a distinct "review app" object. A review app is a **dynamic environment**: an ordinary deploy job whose `environment:name` contains a CI/CD variable, so the name resolves to something different on every branch, and GitLab therefore tracks one environment per branch instead of one shared environment. ```yaml deploy_review: stage: deploy script: - ./deploy.sh "$CI_ENVIRONMENT_SLUG" environment: name: review/$CI_COMMIT_REF_SLUG url: https://$CI_COMMIT_REF_SLUG.review.example.com on_stop: stop_review auto_stop_in: 3 days ``` The first pipeline on branch `feature/add-login` creates the environment `review/feature-add-login`; later pipelines on the same branch append deployments to it rather than creating new ones, because the name is stable *for that branch*. ## Why `CI_COMMIT_REF_SLUG` and not `CI_COMMIT_REF_NAME` `CI_COMMIT_REF_NAME` is the branch or tag name exactly as Git has it: it can contain slashes, uppercase letters, dots and other characters that are illegal in a DNS label or a Kubernetes object name. `CI_COMMIT_REF_SLUG` is the sanitised form — lowercased, everything outside `0-9` and `a-z` replaced by `-`, trimmed of leading and trailing hyphens, and truncated to a length that fits a DNS label. That is why it is the conventional building block for both the environment name and the hostname. Inside the job you often want the sanitised form of the *environment* rather than of the branch: `CI_ENVIRONMENT_SLUG` is derived from the resolved environment name and is guaranteed unique and DNS-safe, which makes it the natural choice for the namespace, release name, or subdomain the script creates. Because the slug is truncated, two very long branch names can in principle collide onto one environment. Teams that care use the merge request IID (`CI_MERGE_REQUEST_IID`) in the name instead, which is short and unique per project. ## What GitLab gives you in return Once the environment exists and its branch has an open merge request, GitLab shows a **View app** button in the merge request widget pointing at `url`. That button is the actual product value: a designer, a product owner or a QA engineer clicks it and sees the branch running, with no local checkout. The environment also appears on the Environments page — grouped into a `review` folder thanks to the slash in the name — with its own deployment history. ## Controlling when review apps are built Review apps are usually restricted to merge request pipelines, so that pushes to the default branch do not create one and a branch with no open MR does not spend cluster capacity. That gating is expressed with the job's rules — for example, matching on the pipeline source being a merge request event — which is standard job-selection configuration rather than anything specific to environments. A second decision is automatic versus manual creation. Making the deploy job manual means a review app exists only when someone asks for one, which is a real lever when the app is expensive to stand up (large database seed, many services). The cost is a click, and the tendency for reviewers to skip it. ## Teardown belongs in the same job Dynamic environments accumulate. Every merged branch leaves behind a namespace, a DNS record, a database and an environment row unless something removes them, and nothing does that by default. Two keywords in the same `environment:` block are the answer: `on_stop:` naming a job that destroys the infrastructure, and `auto_stop_in:` giving the environment an expiry so a forgotten branch cleans itself up. Writing the create half without the destroy half is the single most common defect in a review-app setup. ## Secrets and forks A review app deploys code from an unmerged branch, so treat the credentials it uses as exposed to whoever can open a merge request. Give the review-app deploy a narrowly scoped credential — one that can write only to the review namespace — rather than the same account that deploys production, and be deliberate about whether pipelines from forks may run it at all. Environment-scoped variables help here, because a value scoped to `review/*` never resolves in a production deploy job and vice versa. ## Data is the hard part The YAML is easy; the data is not. A review app needs a database, and pointing several dozen of them at one shared database means one branch's migration breaks everybody else's review app. The realistic options are a per-environment database seeded from an anonymised snapshot, or a shared read-only dataset with per-environment writes — and both cost storage and start-up time, which is what `auto_stop_in` and manual creation exist to bound.
- Why is `CI_COMMIT_REF_SLUG` preferred over `CI_COMMIT_REF_NAME` when building the review app hostname?`CI_COMMIT_REF_NAME` is the raw ref, so a branch called `Feature/Add_Login` yields a string with a slash, an underscore and uppercase letters — illegal in a DNS label and in most Kubernetes names. The slug is lowercased, truncated, and reduced to `a-z0-9-`, so it drops into a hostname or namespace unchanged.
- Two long branch names produced the same review app environment. How is that possible?`CI_COMMIT_REF_SLUG` is truncated to fit a DNS label, so branches that differ only after the cutoff sanitise to the same slug and therefore the same environment name — the second deployment overwrites the first. Use something short and unique in the name instead, such as the merge request IID.
- When would you make the review app deploy job manual rather than automatic?When standing the app up is expensive — many services, a large seeded database, scarce cluster capacity — or when most merge requests never need one. A manual job means capacity is spent only on the branches someone actually wants to look at, at the cost of a click and of reviewers forgetting the button exists.
saying these in an interview costs you the question
- Uses CI_COMMIT_REF_NAME directly in a hostname
- Thinks review apps are a separate GitLab object
- Creates dynamic environments with no stop job
- Assumes GitLab tears the app down after merge
- Reuses the production deploy credential for review apps