A merge request is easier to review when the reviewer can click a link and use the change instead of reading it. GitLab calls that a review app, and it is the feature teams most often skip because provisioning an environment per branch sounds expensive. This post shows a developer doing it on MassiveGRID PaaS, which is built on Virtuozzo Application Platform, formerly Jelastic, where an environment is a clone and a clone costs only the hours it runs.
The developer works on a Node.js API and a PHP back office for a logistics company we will call Tressel Freight. Source lives on the team's self-hosted GitLab server, also on the platform. There are three target environments: staging, production, and a review app per open merge request.
Step one: the boring deploy, done well
Staging and production deploy from Git without CI touching them at all. The platform's Deployment Manager connects a repository and a branch to an application layer and can auto-deploy on a schedule, checking for new commits every minute if you like. For the PHP back office, Tressel points production at the main branch with auto-deploy on and a post-deploy hook that runs composer install, and staging at develop. Deployments on the two-node production layer run sequentially, one node at a time with a 30-second pause, so the back office never shows an error page during a release.
For the Node.js API the developer prefers pushing rather than polling, so she installs the Git-Push-Deploy add-on on the API layer. It registers a webhook on the GitLab project; a push to the configured branch triggers an immediate deployment, with the add-on running the build and restart steps. Both patterns mean the CI pipeline's deploy stage is simply a merge, which is the least fragile deploy step there is.
Step two: an environment per merge request
Review apps are where the platform earns its place. The developer's idea is simple: when a merge request opens, clone the staging environment, deploy the branch to the clone, and post the clone's URL back to the merge request. When the branch merges or closes, delete the clone.
Cloning is a single platform operation that copies the whole environment: the application nodes, the database with its data, the configuration and the mounted storage, in a few minutes. The clone gets its own platform subdomain, which is exactly the shareable URL a reviewer needs. The platform's CLI exposes the same operation, so the pipeline runs it from a runner.
review:
stage: review
image: alpine
before_script:
- apk add --no-cache curl bash python3
script:
- ./ci/paas-clone.sh staging "review-mr-$CI_MERGE_REQUEST_IID"
- ./ci/paas-deploy.sh "review-mr-$CI_MERGE_REQUEST_IID" "$CI_COMMIT_SHA"
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://review-mr-$CI_MERGE_REQUEST_IID.app.paas.massivegrid.com
on_stop: stop_review
rules:
- if: $CI_MERGE_REQUEST_IID
stop_review:
stage: review
script:
- ./ci/paas-delete.sh "review-mr-$CI_MERGE_REQUEST_IID"
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
rules:
- if: $CI_MERGE_REQUEST_IID
The three helper scripts wrap the platform CLI's environment control commands: clone, deploy from the branch, and delete. The API token lives in a masked GitLab CI variable. GitLab's environment keyword makes the review URL appear as a button on the merge request, and on_stop ties deletion to the branch being merged or closed.
What a review app costs
This is the part that makes review apps viable. A cloned environment is billed like any other: per cloudlet-hour on what its containers actually use, and a review app used by one reviewer for twenty minutes uses very little. Tressel's staging environment is two small application nodes and a database, roughly 20 cloudlets an hour when idle. A review app that lives for a working day therefore costs about 20 cloudlets times 8 hours times $0.003372, or around 54 cents before discounts. Ten open merge requests at once are a few dollars a day. The developer added one more economy: the pipeline's stop_review job also runs automatically on a nightly schedule for any review environment older than three days, using the CLI to list and delete them.
| Environment | Lifetime | Typical size | Approximate cost |
|---|---|---|---|
| Production | Always on | 2 app nodes + DB, scales up on load | Monthly, by usage |
| Staging | Always on, stopped at weekends by the scheduler add-on | Same topology, smaller limits | Monthly, by usage; disk only while stopped |
| Review app | Open to merged, at most 3 days | Clone of staging | Cents per hour of life |
Keeping review apps from touching production
A clone carries its parent's configuration, including database hostnames and any third-party credentials in environment variables. Tressel handles this two ways. Staging's configuration already points at staging services, so the clones inherit safe targets. And production sits in its own network-isolated environment group, so even a mistaken hostname in a review app cannot reach production's database over the internal network. The review apps live in a group of their own, which also gives the team one place to see how many exist and what they cost.
Data in review apps
Because a clone copies the database, every review app starts with staging's data, which is anonymised on purpose. For merge requests that include a schema migration, the developer's helper script runs the migration on the clone after deployment; if it fails, the merge request shows a failed job before anyone reads a line of code. That is the second benefit of review apps that teams underrate: migrations get rehearsed on real-shaped data, per branch, for free.
The whole loop
Open a merge request; within five minutes there is a URL on it. Push a fix; the same environment redeploys. Merge; the environment is deleted and develop auto-deploys to staging within a minute. Tag a release, merge to main; production updates one node at a time. The pipeline file is about forty lines, most of them the two jobs above, and the platform does everything that would otherwise be Terraform.
Frequently Asked Questions
Do I need the platform CLI installed on the runner, or can I call the API directly?
Either. The CLI is a thin wrapper over the platform's REST API and can be installed with one script on the runner image, or the helper scripts can call the same API endpoints with curl. The CLI is more readable in a pipeline; the API needs no installation.
Can review apps have their own subdomain under our company domain?
Yes. Bind a wildcard DNS record for your review domain to the platform's shared load balancer, then bind each clone to its subdomain via the CLI as part of the clone script. Alternatively use the platform subdomains, which need no DNS at all and are already served over HTTPS.
What about review apps for a Docker-based application?
The same approach works. Cloning copies custom Docker containers with their volumes, and the deploy step becomes a container redeploy to the image tag built by the pipeline, which the CLI also supports.
A URL on every merge request
Run your GitLab server and your application environments on MassiveGRID PaaS, clone staging per branch from the pipeline and pay only for the hours a review app lives. Free 14-day trial, no credit card.
GitLab Server on MassiveGRID PaaS