Preview Environments Without Vercel: Branch Deploys on Your Own VPS
Cloud

Preview Environments Without Vercel: Branch Deploys on Your Own VPS

A preview URL per branch is the one Vercel feature people miss most after leaving. It's buildable on your own server, and it's genuinely harder than Vercel makes it look — mostly because of the database, which nobody's marketing page mentions.

Published September 23, 202610 min readUpdated Sep 6, 2026

Written by · Full-Stack Agentic AI Software Engineer — AI Agents, Automation & Revenue Systems for GTM/RevOps teams

In brief

Can you get Vercel-style preview deployments — a live URL for every branch or PR — on a self-hosted VPS, and what does it actually take?

Yes, mechanically: a wildcard DNS record, a naming convention that turns a branch name into a subdomain, and Coolify (or a script wrapping Docker directly) standing up one container per branch and tearing it down when the branch closes. The part Vercel's version hides from you entirely is the database — every ephemeral container either needs its own throwaway data or has to share one carefully, and there's no clean answer, only trade-offs. Teardown discipline is what keeps a handful of branches from quietly becoming a resource leak on a box that doesn't have room for one.

  • A wildcard DNS record (*.preview.example.com) plus a naming convention (branch name -> subdomain) is the entire routing mechanism — nothing exotic beyond that
  • Each preview is an ephemeral container built the same way production is, just with a shorter lifespan and a different subdomain injected as an env var
  • The database is the genuinely hard part: a full copy per preview is expensive and slow to provision, a shared database risks one preview's test data corrupting another's, and there's no option here that's simply free
  • Teardown has to be automatic and triggered by something real (a closed PR, an inactivity window) or previews accumulate quietly until the VPS runs out of room for the app they're supposed to be previewing

Evidence notes

Coolify's per-deployment preview support

Coolify can spin up a preview deployment per pull request with its own subdomain, tied to the PR's lifecycle in the connected git provider, checked September 2026.

Cloudflare free-tier DNS

The free Cloudflare plan supports wildcard DNS records and up to 1,000 DNS records per zone, more than enough for a branch-preview naming scheme on a personal or small-team project, checked September 2026.

Contabo Cloud VPS 4 pricing

4 vCPU, 8 GB RAM, 100 GB SSD, €5.50/month for the first 24 months (promotional, EUR), verified against Contabo's own pricing page, checked September 2026.

Continue with purpose

The one thing people bring up, unprompted, when they hear I moved off Vercel is preview URLs — open a PR, get a live link, close the PR, link disappears. It's a genuinely good feature. It's also not something Vercel invented so much as something Vercel made feel free, and the parts it makes feel free are exactly the parts that turn out to cost real thought once you're the one running the server.

Nothing about rebuilding it is exotic — every piece is a primitive Coolify and Cloudflare already have. What's actually missing from most 'preview environments on your own server' write-ups is any honest accounting of what those primitives cost once there's more than one preview alive at a time, and what happens to the app's data underneath them. That's the part worth being specific about, rather than stopping at 'point a wildcard record at your server and you're basically done.' There is a whole operational layer above preview environments without Vercel that XenGrowth's revenue operations work documents.


Vercel preview deployments

Self-hosted (Coolify + wildcard DNS)

Setup

Automatic — no configuration beyond connecting the repo

Wildcard DNS record, a CI workflow on pull_request events, Coolify's API wired in

Database per preview

Handled by whatever managed DB the project already uses, often with branch-per-preview support built in

Your decision entirely — full copy, shared, or per-schema, each with a real trade-off

Teardown

Automatic on PR close, no extra thought required

Needs an explicit webhook plus an inactivity sweep as backstop, or containers accumulate

Resource cost model

Someone else's infrastructure absorbs the extra containers

Every open preview is RAM on the exact box that also runs production

What's the actual routing mechanism behind a preview URL?

One wildcard DNS record — `*.preview.example.com` pointed at the VPS — and a naming convention that turns a branch name into a subdomain: `feature-checkout` becomes `feature-checkout.preview.example.com`. That's the entire routing trick. Cloudflare's free tier supports wildcard records without issue, and the reverse proxy on the server (Traefik, in Coolify's case) reads the subdomain on each incoming request and routes it to whichever container was tagged to answer for that name. Nothing about this needs a second server, a second IP, or anything beyond what a single-box Coolify setup already has running.

Where it gets more interesting than 'point DNS at a box' is the branch-to-container mapping actually being created and destroyed on a schedule nobody's watching manually. A CI workflow, triggered on `pull_request` open/synchronize/close events, builds an image the same way a production build does, tags it with the branch name instead of a git SHA meant for rollback, and tells Coolify (via its API) to create or update a deployment scoped to that subdomain. Close the PR, and the same webhook tells Coolify to tear the container down.

Are these containers actually cheap to run, or just cheap to create?

Cheap to create, not free to run. Each preview is a full instance of the app — same image format, same runtime footprint as production, just answering on a different subdomain and, usually, a smaller or shared slice of the database. Three or four previews sitting idle on an 8 GB box is a meaningfully different resource picture than one production container, and it's the kind of cost that's invisible until it isn't — nothing alerts you to it, because nothing's actually broken, right up until a build competing with three live previews for RAM finally is. A team that reviews five or six branches a week, each keeping its preview open until merge, can easily be running more containers than it realizes on any given afternoon. That failure mode isn't hypothetical — it's the exact one a VPS running low on memory hits, just triggered by preview sprawl instead of a build. The XenGrowth resource library works through the operations side of this in more operational detail.

  • A preview container's runtime footprint is the same as production's — smaller traffic, not smaller memory usage per container

  • Every open PR with a preview attached is one more container permanently occupying RAM until it's explicitly torn down

  • A stale branch nobody's looked at in weeks still costs exactly as much as one being actively reviewed right now

  • Idle previews don't show up as an error anywhere — they show up as less headroom, discovered only when something else needs it

What's genuinely the hard part here — and where does it actually break?

The database. Everything else in this setup is a solved, mechanical problem; the database is the one place where every option has a real cost and none of them is obviously correct. Give each preview its own throwaway database, seeded fresh on creation, and previews are fully isolated from each other — but seeding a realistic dataset on every container start is slow, and 'realistic' data for a preview usually means either a stale, hand-maintained fixture or a sanitized production snapshot, both of which need someone to keep them current. Point every preview at one shared staging database instead, and provisioning is instant — but now one preview's test data, or a migration a branch is trying out, can visibly corrupt what every other open preview is looking at, and two people reviewing two different PRs can watch each other's changes appear in their own preview by accident.

There isn't a clean third option, and it's worth being honest about that instead of implying one exists. A schema-per-preview approach — one Postgres instance, a separate schema created per branch, connection string pointed at that schema — is the closest to a middle ground: cheaper than a full database per preview, less contaminated than one shared database, but it means every migration has to be applied per-schema on preview creation, which is one more thing that can silently fail and leave a preview looking broken for reasons that have nothing to do with the code being reviewed.

Picking between these isn't really a technical question so much as a question about what a preview is actually for on a given team. A preview meant to answer 'does this render correctly' barely needs real data at all — a small, static fixture seeded once and reused across every preview is enough, and it sidesteps the whole database problem by making the data intentionally boring. A preview meant to answer 'does this migration behave correctly against production-shaped data' needs something closer to a real snapshot, and that's the case where the cost of full isolation is worth paying, because the entire point of that preview is testing the thing a shared database would silently protect it from ever encountering. If AI agents and marketing automation is the part you are stuck on, XenGrowth on governed AI marketing workflows is the better reference.

Approach

Isolation

Setup cost per preview

Where it breaks

Full database per preview

Complete

Highest — provisioning and seeding take real time

Seed data goes stale or unrealistic if nobody maintains it

One shared staging database

None

Lowest — instant

One preview's data or migration visibly affects every other open preview

Schema-per-preview, one Postgres instance

Partial

Medium — a migration step per schema, per preview

A per-schema migration failure breaks one preview silently, unrelated to the code change

How do you keep staging and production apart on the same box?

The same primitives that separate one preview from another separate staging from production: a distinct subdomain, a distinct container, and — non-negotiably — a distinct database, never a shared one. Staging sharing production's database is a different category of risk than two previews sharing a staging database, because staging is where people deliberately run destructive tests, seed unrealistic data, and try things they wouldn't dare in production; none of that is supposed to be reversible-by-luck. Coolify's environment scoping handles the container and subdomain side of this cleanly — separate 'environments' within one project, each with its own env vars — but the database separation is a decision made once, by hand, and it's worth treating as the one thing in this whole setup that isn't allowed to have a shortcut.

It's tempting, on a single small VPS, to reach for 'just point staging at a read replica of production' as a compromise. Resist it. A read replica still means staging can see real user data, which turns every staging environment into something that has to be locked down and audited exactly like production, defeating the entire reason staging exists as a place to experiment freely. A staging database seeded from an anonymized or synthetic dataset costs more to set up once and saves that entire category of risk permanently.

What actually triggers teardown, and what happens if nothing does?

Teardown needs a real trigger, not a hope that someone remembers. The two that actually work: the git provider's own PR-closed webhook (GitHub fires this reliably whether the PR was merged or just closed), and a scheduled sweep that kills any preview container past an inactivity window — say, no new commits to that branch in seven days — as a backstop for the branches that never got a formal close. Relying on only the first misses long-abandoned branches that were never closed, just forgotten; relying on only the second means a merged PR's preview lingers for up to a week doing nothing useful. On AI search, GEO and discovery specifically, XenGrowth on building one SEO and GEO content system is worth reading.

  1. PR closed or merged: the webhook fires immediately, Coolify tears the container down within the same CI run

  2. Branch inactive past a set window: a scheduled job checks last-commit timestamps and tears down anything past the threshold

  3. Preview build itself fails: the failed container never gets a subdomain routed to it in the first place, so nothing needs tearing down

  4. Manual override: a maintainer can force-close a preview from Coolify's dashboard when a trigger genuinely didn't fire

Without either trigger, this degrades exactly the way any unmonitored resource does: quietly, then all at once. A handful of dead containers doesn't crash anything by itself — it just erodes the RAM headroom a real deploy needs, on the same box, until the day a legitimate build lands on a server that's already three forgotten previews deep into its memory budget. Whatever tears containers down should also free the subdomain and drop the preview's database schema or throwaway instance in the same step — a leftover schema nobody's using is a smaller problem than a leftover container, but it's still one more thing that eventually needs explaining to whoever inherits this setup.

Vercel's preview URLs feel free because the database problem is Vercel's problem, solved once for everyone. Self-hosted, it's solved once, by you, for exactly your app — which is more work and also the only version where you actually understand what happens when it breaks.

None of that makes previews not worth building. It makes them worth building deliberately, with the database decision made up front and named in whatever documentation the rest of the team reads, rather than discovered by whoever opens the second PR and wonders why their preview already has someone else's test account in it.

Where this fits with the rest of the deployment setup

Preview environments are a branch of the same pipeline that ships production, not a separate system. How Next.js gets self-hosted on Coolify in the first place covers the base every preview container is built from, and the full git-push-to-production pipeline covers the production path this one branches off of. And if the RAM cost of a few extra containers still sounds abstract, what actually happens when a VPS runs out of memory is the concrete version of the warning in this post.

Further reading from XenGrowth

Where this work meets go-to-market

Working on preview environments without Vercel inside a commercial team? the team at XenGrowth publishes operator guides on the revenue side of this work.

What makes a preview environment actually useful?

Four questions on branch deploys you run yourself. The container is the easy part; the interesting decisions are about data and cleanup.

1 / 4
What should a preview environment use for its database?

Apply this article

How to turn insights into execution

A practical sequence for teams turning concepts into production outcomes.

preview environmentsCoolifyself-hostingCloudflareCI/CDcloud

Audit your current state

Map the bottlenecks and constraints connected to the article’s core problem.

Choose one bounded change

Test the most useful recommendation on one workflow before widening the scope.

Measure what changed

Keep the parts that improve the work, document what failed, and make the next decision from evidence.

Next step

Need help applying this in your stack?

I can translate these patterns into a concrete implementation plan for your team.

Discuss implementationBack to blog

Replies usually within 24 hours.

Next Steps

Continue reading

My Complete Self-Hosted Stack for SaaS in 2026

Contabo, Coolify, Docker, Cloudflare, Postgres, R2, Resend, Uptime Kuma, Turborepo and Docker Hub. Here's every piece of the stack I actually run, what each one replaced, and why I picked it over the alternatives.

Navigate

Why I Push to Docker Hub Instead of Building on My $10 Server

Build-elsewhere-pull-here isn't a preference, it's the only version of this that doesn't put a compile job in direct competition with the app for the same 8 GB. Here's what that split actually buys, and where a private registry earns its keep instead.

Navigate

Git Push to Production: My Self-Hosted Deployment Workflow End to End

No platform button, no black box. A monorepo commit turns into a running container on my own server through Turborepo, GitHub Actions, Docker Hub and Coolify — here's every step, including the ones that broke on me first.

Navigate

The Contabo + Coolify + Cloudflare Stack That Replaced My Vercel Bill

Three pieces, not thirty. Here's exactly what each one does, why that specific combination and not one of the dozen others I considered, and which parts of Vercel each one is actually standing in for.

Navigate

The Simplest Production Architecture for a Bootstrapped SaaS

One box, a handful of managed pieces around the edges, and a very short list of things you're not allowed to build yet. Here's the architecture, priced out to $20 a month, and the exact signal that tells you when to add each thing you skipped.

Navigate

Moving a SaaS Off Vercel to a €5.50 VPS: What the Numbers Actually Look Like

Nobody publishes their real Vercel invoice, so most migration posts trade in vibes instead of arithmetic. This one builds the comparison from Vercel's and Contabo's own published rates, states every assumption out loud, and shows exactly where the two lines cross.

Navigate

My Free Monitoring Stack for Self-Hosted Apps

Vercel gives you monitoring whether you ask for it or not. A VPS gives you a blank terminal and the assumption you'll figure it out. Here's what to actually watch on a self-hosted box, with tools that cost nothing, and why watching from the box itself is the one setup that will lie to you.

Navigate

Should Your Database Live on the Same VPS as Your App?

The pitch for co-location is real: no network hop, no egress bill, one box to back up. So is the failure mode — one OOM event takes the app and the database down together, because they were never separate to begin with.

Navigate

Cloudflare Tunnel vs Reverse Proxy: Which One Should You Use?

One of these opens no inbound ports and works behind CGNAT. The other is simpler, portable, and doesn't ask you to trust a daemon or a vendor's uptime with every request. Neither one is the obviously correct default — the right answer depends on which failure you'd rather own.

Navigate

Best VPS Providers for Coolify: Contabo, Hetzner and the 2026 Price Shift

Every 'best VPS for Coolify' post still says Hetzner is the cheap option. That stopped being reliably true in June 2026 for two of its most popular tiers, and nobody's updated the recommendation yet.

Navigate