
September 09, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Preview Environments for Every PR give reviewers a live, isolated copy of your application before merge. A reviewer clicks a link on the pull request and sees the exact branch running with its migrations, assets, and config applied. That beats screenshots and long staging deploy threads. On production Laravel applications I've maintained with GitLab CI and Deployer 7, preview URLs cut review cycles from days to hours. This guide walks through architecture, CI config, Laravel-specific setup, cost control, and cleanup — the same patterns I use on sister sites sharing a Deployer pipeline.
What are Preview Environments for Every PR?
A preview environment is a short-lived deployment tied to one branch or pull request. It gets its own URL, database schema, and runtime config. Reviewers test real flows — login, checkout, file upload — without touching staging or production.
The pattern differs from a shared staging server. Staging holds one mutable copy of the app. Preview environments are disposable. Each PR gets a fresh namespace. Merge the PR and the environment disappears.
In practice, three layers make this work:
- Provisioning — create a host, container, or namespace for the branch.
- Deploy — build assets, run Composer, apply migrations, warm caches.
- Feedback loop — post the preview URL as a CI comment and run automated checks against it.
If you already run staging, treat it as the baseline. Read our guide on setting up a staging environment that mirrors production before adding per-PR previews on top.
Platforms like GitLab call these Review Apps. GitHub Actions uses environment URLs. Kubernetes teams often use namespace-per-branch with Helm or Argo CD. The naming differs. The goal is the same — one live URL per PR.
How do you choose a platform for preview environments?
Pick the option your team can run and debug at 2 a.m. A fancy Kubernetes setup helps nothing if nobody understands ingress rules.
| Approach | Best for | Trade-off | Typical cost |
|---|---|---|---|
| GitLab Review Apps | Teams already on GitLab CI | Tied to GitLab; needs a runner with deploy access | Rs 3,000–8,000/mo (~USD 22–60) per small VM |
| GitHub Actions + SSH deploy | GitHub repos with existing Deployer scripts | You write teardown logic yourself | Runner minutes + one preview host |
| Docker Compose on a preview host | Laravel/PHP monoliths, small teams | Manual port and subdomain routing | One EC2 or VPS, Rs 5,000/mo (~USD 37) |
| Kubernetes namespace per PR | Microservices, high PR volume | Cluster ops overhead; needs ingress + cert automation | Cluster baseline + per-pod CPU |
| PaaS (Render, Railway, Fly.io) | Fast proof of concept | Less control; vendor pricing at scale | Pay per preview hour |
For Laravel monoliths on a single Ubuntu server, Docker Compose plus Nginx virtual hosts is often enough. I've used this on legal-tech portals and booking systems where PR volume stays under twenty per week. For higher volume, read about Terraform workspaces and environments to keep infra definitions repeatable.
Official references: GitLab Review Apps documentation and GitHub Actions environment deployments.
How do you set up preview environments in GitLab CI?
GitLab Review Apps are the fastest path if you already use GitLab CI. The pipeline builds the app, deploys to a predictable subdomain, and posts the URL on the merge request.
Step 1 — Define a dynamic environment name
GitLab derives the environment from the branch slug. Keep names DNS-safe and short.
# .gitlab-ci.yml excerpt
deploy_preview:
stage: deploy
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.preview.example.com
on_stop: stop_preview
script:
- dep deploy preview-$CI_COMMIT_REF_SLUG
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Step 2 — Add a stop job for teardown
Without a stop job, preview hosts fill up fast. GitLab triggers on_stop when the MR closes or merges.
stop_preview:
stage: deploy
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
script:
- dep deploy preview-$CI_COMMIT_REF_SLUG --task cleanup
when: manual
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
Make teardown automatic in practice by calling cleanup from a webhook or scheduled job. Manual stop jobs get forgotten.
Step 3 — Post the URL back to the merge request
GitLab does this automatically when environment.url is set. Reviewers see a "View app" button on the MR widget. Pair it with AI code review in your CI pipeline so static analysis and live preview happen in one pass.
How do you configure preview environments for Laravel applications?
Laravel apps need a few extra steps beyond a generic static site. You must handle .env, migrations, queued jobs, and compiled assets. PHP 8.3 or higher is required for Laravel 13; Laravel 12 runs on PHP 8.2+. Match your production PHP-FPM version on the preview host.
Environment file strategy
Never copy production secrets into previews. Generate a preview-specific .env from CI variables:
APP_ENV=preview
APP_URL=https://${BRANCH_SLUG}.preview.example.com
APP_KEY=base64:...generated per preview...
DB_DATABASE=preview_${BRANCH_SLUG}
QUEUE_CONNECTION=sync
MAIL_MAILER=log
Disable outbound mail and payment gateways in preview. Route webhooks to a mock endpoint or log driver. On a client project with eSewa integration, I pointed preview callbacks to a JSON formatter tool endpoint during QA — never to live payment APIs.
Database isolation
One schema or database per preview is the safe default. Shared preview databases cause migration conflicts when two PRs alter the same table.
- Create database
preview_feature_slugbefore deploy. - Run
php artisan migrate --forceagainst that database. - Optionally seed with anonymised fixtures — never production dumps with PII.
- Drop the database in the stop job.
Read our notes on database migrations in team environments before running parallel migration paths across previews.
Asset builds and Deployer 7
My production servers often have no Node.js installed. I build frontend assets in CI with Node.js 26 LTS and commit or rsync the public/build directory to the preview release. Deployer 7 handles symlinked releases the same way as production:
# deploy.php excerpt
host('preview-' . getenv('BRANCH_SLUG'))
->setHostname('preview.example.com')
->set('deploy_path', '/var/www/previews/{{branch_slug}}')
->set('branch', getenv('CI_COMMIT_REF_NAME'));
After symlink swap, reload PHP-FPM so opcache picks up the new code. This is the same step that fixes stale-code bugs on production deploys. See the Laravel 12 deployment guide for optimisation commands like config:cache and route:cache.
Nginx routing for subdomains
Wildcard DNS (*.preview.example.com) plus one Nginx template covers all branches:
server {
listen 443 ssl;
server_name ~^(?<branch>[a-z0-9-]+)\.preview\.example\.com$;
root /var/www/previews/$branch/current/public;
# ... standard Laravel try_files ...
}
Issue a wildcard TLS cert with Let's Encrypt DNS validation. HTTP-only previews leak session cookies and fail mixed-content checks on asset URLs.
For local parity before pushing, use Docker Compose for local Laravel development. Match PHP 8.5 or 8.4 locally if production preview runs 8.4.
What does it cost to run preview environments for every PR?
Cost scales with PR count, preview lifetime, and resource size. A single small EC2 instance can host five to ten lightweight Laravel previews if you cap idle time.
Rule of thumb for Nepal-based teams on budget hosting:
- Preview host: Rs 5,000–12,000/month (~USD 37–90) for a 4 GB RAM VPS.
- Wildcard DNS + TLS: free with Cloudflare or Let's Encrypt.
- CI minutes: included on GitLab.com free tier for small teams; self-hosted runners cost electricity and ops time.
- Database storage: grows if previews are not torn down — budget disk alerts.
Cost controls that actually work:
- Auto-stop after 48 hours — cron deletes environments with no MR activity.
- Deploy previews only on label — add
deploy:previewlabel to skip docs-only PRs. - Shared Redis, isolated DB — one Redis 8.10 instance with key prefixes saves RAM.
- Skip previews for dependency-bot PRs — run tests only, no deploy.
On a Laravel + Livewire booking project I shipped, preview deploys ran only when the MR touched backend or frontend code. Documentation PRs skipped deploy and saved roughly forty percent of CI time.
For Symfony apps, similar patterns apply — see Symfony environment config for multi-environment apps for env var handling across preview and staging tiers.
How do you secure and clean up ephemeral preview environments?
Preview URLs are often guessable subdomains. Treat them as semi-public. Never expose real customer data. Never point previews at production databases.
Authentication and access
Options ranked by simplicity:
- HTTP basic auth on the Nginx vhost — good enough for internal QA.
- VPN or IP allowlist — restrict to office and CI runner IPs.
- OAuth gate middleware — Laravel middleware that checks GitLab/GitHub group membership.
Disable admin panels and Horizon dashboards on previews unless behind auth. I've seen staging admin URLs indexed by Google. Add noindex headers and robots disallow on preview hosts.
Secrets and third-party APIs
Use sandbox API keys for Stripe, PayPal, and local gateways like Khalti in preview mode. Set APP_DEBUG=false even on previews — debug pages leak env keys. Rotate preview APP_KEY per environment; never reuse production keys.
Reliable teardown
Orphaned previews are the main long-term cost. Your stop job must:
- Remove the Deployer release directory.
- Drop the preview database.
- Delete the Nginx vhost and reload.
- Clear Redis keys with the branch prefix.
- Remove DNS record if not using wildcard.
Schedule a nightly reconciler that lists active preview directories and compares them to open MRs. Delete anything stale. Sister sites on our shared Deployer 7 pipeline use this pattern — the same hosts that run Notary Kathmandu and related legal-tech portals.
Automate server provisioning with Ansible playbooks for PHP server provisioning so preview hosts stay consistent. For ongoing ops, support and maintenance contracts should include preview host monitoring.
Key Takeaways
- Preview Environments for Every PR give reviewers a live URL tied to the branch — not a shared staging slot.
- GitLab Review Apps or GitHub Actions plus Deployer 7 cover most Laravel teams without Kubernetes.
- Isolate databases per preview; never attach production data or live payment keys.
- Build frontend assets in CI with Node.js 26 LTS when the server has no Node installed.
- Auto-teardown on MR close is non-negotiable — schedule orphan cleanup as a safety net.
- Gate deploys with labels or path filters to control cost on high-volume repos.
People Also Ask
What is the difference between staging and a preview environment?
Staging is a shared, long-lived environment that mirrors production config. A preview environment is ephemeral and tied to one PR. Staging validates release candidates. Previews validate individual feature branches before they merge.
Can you run preview environments without Kubernetes?
Yes. A single Ubuntu VPS with Nginx, PHP-FPM, MySQL, and Deployer 7 handles multiple Laravel previews via wildcard subdomains. Kubernetes helps at scale but adds operational overhead most small teams do not need.
How long should a preview environment stay alive?
Keep previews alive while the MR is open, plus a short grace period of 24–48 hours. Auto-delete after merge or close. Long-lived previews waste disk, clutter DNS, and drift from the branch tip.
Do preview environments work with WordPress or WooCommerce?
They do, but database copies are heavier. Clone a sanitised snapshot, run search-replace on URLs, and disable outbound email. WooCommerce 11.1 preview sites should use test payment gateways only. For WordPress-specific hosting, see our WordPress development service.
Ship faster with live PR previews
Preview Environments for Every PR turn code review from async guesswork into clicked-path verification. Start with one preview host, GitLab Review Apps or a GitHub Actions deploy job, isolated databases, and a teardown script you trust. Expand to Kubernetes only when PR volume demands it.
If you want this wired into your Laravel app, existing CI pipeline, or client portal project, I can help design and deploy the full stack. Explore enterprise application development and testing and optimization services, or custom software development for greenfield setups. Need a second opinion on your current pipeline? Contact us and describe your repo, host, and PR volume — we'll map the lightest path to Preview Environments for Every PR that your team can maintain.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

