Three platforms that share almost nothing else in common — a venture-backed hosted PaaS, a developer-first deploy tool, and a free self-hosted Docker panel — all shipped the same feature this cycle: spin up a live, isolated environment automatically for every pull request. Render gates it behind a Professional workspace plan. Railway ships "Focused PR Environments" that only rebuild the services a diff actually touches, plus opt-in support for bot-authored PRs from Dependabot, Renovate, and Claude Code. Dokploy — open source, self-hosted, free — gives you the same idea with zero configuration: open a PR, get a traefik.me link in a bot comment, close the PR, the environment disappears.
That convergence is the story. PR-triggered preview environments stopped being a premium upsell and became the thing you're expected to have, the same way HTTPS-by-default and one-click rollback did before it. For anyone building a self-hosted, git-push PaaS, that raises an uncomfortable engineering question: how much of what Dokploy does for free on one box survives the jump to a real multi-node fleet — and where does the checkbox stop matching the amount of infrastructure work behind it?
The Feature Checklist, As It Stands in Mid-2026
Here's the state of the art across the three reference points worth benchmarking against:
| Platform | Trigger | Domain/TLS | Teardown | Gating |
|---|---|---|---|---|
| Render | GitHub PR opened | Auto-generated subdomain + managed TLS | previews.expireAfterDays, manual close | Professional plan or higher |
| Railway | GitHub PR opened | Auto-generated subdomain | Deleted on PR merge/close | Free on all plans; bot PRs opt-in |
| Dokploy | GitHub PR opened (opt-in per app) | Free traefik.me wildcard domain, no config | Automatic on PR close; capped at 3 concurrent by default | Free, open source |
The pattern across all three: the domain and TLS problem is solved before the developer ever asks for it, and teardown is tied to a webhook, with a fallback limit or expiry window as a backstop. Those two properties — zero-touch HTTPS and self-cleaning infrastructure — are exactly the two hardest things to build once you stop running on a single host. Dokploy and Kamal-style tools get them almost for free. A Cluster API fleet doesn't.
Why the Single-Host Model Is the Easy Version
It's worth being precise about why Dokploy and Kamal make this look trivial, because every piece of that simplicity comes from one shared assumption: everything lands on the same machine, behind the same reverse proxy, at the same IP address.
The mechanics, concretely:
- One proxy owns all the routing. Traefik (Dokploy) or
kamal-proxy(Kamal 2) sits in front of every container on the host and routes by hostname. Adding a PR environment means adding one more container and one more routing rule — not touching networking. - The domain trick needs no DNS record.
traefik.meand services like it resolve<anything>.traefik.meto the IP address embedded in the subdomain itself (10-0-0-5.traefik.meresolves to10.0.0.5). Because the host's IP is fixed and known in advance, there's no DNS API call, no propagation delay, and no per-PR record to create or delete. - TLS is one certificate for one IP. Traefik requests (or self-signs) a certificate scoped to that single, stable address. It doesn't need to mint a new cert per PR because the underlying identity — the IP — never changes.
- Teardown is a local process kill. Closing the PR fires a webhook, the platform stops and removes one container, and the proxy's routing table drops the rule. No cross-machine coordination, no scheduler decision, no risk of the deletion landing on the wrong node.
- Cost is capped by definition. You have one host with a fixed amount of CPU and RAM. Dokploy's own default limit of 3 concurrent preview deployments per app isn't really a cost control — it's an admission that the box only has so much room. Abandon a preview and worst case you've wasted a slice of a machine you already own and pay for.
None of that requires a scheduler, a DNS API, a certificate authority integration, or a garbage collector. It requires a reverse proxy and a webhook. That's the entire trick — and it's also exactly the set of assumptions that stops holding the moment "the platform" means a fleet of machines instead of a box.
What Breaks First: Placement, Not Just Provisioning
A Cluster API–based platform manages a fleet — multiple Machine objects, multiple nodes, workloads scheduled across whichever node has room. The first question a PR-preview feature has to answer isn't "how do I create a namespace," which is one API call; it's where does this namespace's pods actually run, and what does that decision cost.
There are two honest architectures, and they trade off in opposite directions:
- A dedicated ephemeral workload cluster per PR (a real Cluster API
Clusterobject, provisioned and torn down per pull request). This gives the strongest isolation — a misbehaving preview can't starve a neighbor — but it means paying full node-boot latency (bare-metal or VM provisioning measured in minutes) and full node cost for every PR, including the one-line typo fix that gets merged in ten minutes. - A namespace in a shared, multi-tenant cluster, bin-packed onto node capacity that already exists. This is what the cost profile of "every PR gets a preview" actually demands — Render, Railway, and Dokploy all default to something in this spirit, because per-PR full-cluster provisioning would make the feature financially absurd at any real PR volume.
Once you pick the shared-cluster model — the only one that's economical — you've signed up for exactly the problem single-host tools never have. The cluster autoscaler now has to decide whether an incoming PR namespace fits on an existing node or justifies buying a new one, and a ResourceQuota per namespace becomes mandatory rather than optional, because nothing physically stops one PR's build from consuming an entire node the way a single host's RAM ceiling implicitly did. Bin-packing PR namespaces onto existing capacity is a scheduling problem Dokploy never has to solve, because Dokploy only ever has one node to place anything on.
The Domain and TLS Problem, at Fleet Scale
traefik.me works because there is exactly one IP address to encode in the subdomain. A Cluster API fleet has many nodes behind a shared ingress layer, fronted by a load balancer whose IP is stable but whose backing pods are scattered across the fleet — so the "IP-in-the-subdomain" trick has nothing fixed to point at, and even if it did, you don't control a *.traefik.me-style third-party zone to make it match your own platform's domain.
The real mechanism for fleet-scale preview environments looks like this instead:
- One wildcard certificate covering the platform's whole preview domain —
*.pr.yourplatform.dev— issued once viacert-managerusing a DNS-01 challenge (mandatory for wildcards; the simpler HTTP-01 challenge doesn't support them) against the DNS zone the platform actually owns. - Cross-namespace secret propagation. The issued TLS secret lives in the
cert-managernamespace, but every new PR namespace's ingress needs to reference it. Either the ingress controller is configured with that wildcard as its cluster-wide default certificate, or a controller like Reflector watches for new namespaces and copies the secret into each one automatically. - One DNS record, not one per PR. Because the certificate and the ingress are wildcard-scoped, a single
*.pr.yourplatform.devA/CNAME record (or an ingress-controller-managed load balancer IP) covers every PR namespace that will ever exist — new PR subdomains work immediately without touching DNS at all.
The payoff of getting this right is that it converges back to the same zero-touch experience Dokploy gives away for free — a new PR gets working HTTPS the instant its namespace exists — but the path there runs through DNS zone ownership, a wildcard DNS-01 issuer, and a secret-propagation controller instead of one string-encoded IP trick. It's more moving parts producing the same end-user result, which is exactly why it's easy to underestimate from the outside.
Teardown That Survives the Case Nobody Tests: The Webhook That Never Fires
The happy path for teardown is identical across every platform in this comparison: PR closes, a webhook fires, the environment gets deleted. That path is not the hard part, and it's not where multi-node infrastructure earns its reputation for being harder.
The hard part is the path where the webhook doesn't fire — a GitHub outage, a force-push that confuses the integration, a bot-opened PR that gets abandoned rather than formally closed. On a single host, a forgotten preview container is a rounding error: it sits on a box you already pay for at a fixed monthly rate, consuming a few hundred MB of RAM until someone notices. On a shared multi-tenant Kubernetes fleet with an autoscaler, the same forgotten namespace can be the difference between fitting on existing nodes and the autoscaler provisioning a new one to make room — turning one missed webhook into an ongoing, metered bill instead of a rounding error.
This is precisely the failure mode FinOps teams are calling out as a 2026 priority: unclosed non-production environments are consistently cited among the largest sources of avoidable cloud waste, with cleanup of abandoned dev/test and proof-of-concept resources reported to save organizations in the range of $4,200 to $18,500 in the first 30 days alone once someone actually goes looking. A preview-environment feature that only tears down on a webhook is a feature that quietly assumes a webhook will always fire.
The fix a fleet-based platform has to build — and that Dokploy, running on one box with a hard concurrency cap, never has to think about — is a backstop that doesn't depend on the webhook at all: every PR namespace gets a ResourceQuota at creation and an expiry timestamp as a label or annotation, and a separate TTL-controller loop sweeps the cluster on its own schedule, deleting anything past its expiry regardless of whether GitHub ever told the platform the PR closed. The webhook stays the fast path for the common case; the TTL sweep is the one that actually caps the bill.
The Real Delta: Four Subsystems, Not One Checkbox
Line up the two models side by side and the "materially harder" claim stops being a vibe and becomes a list:
| Subsystem | Single host (Dokploy/Kamal) | Multi-node fleet (Cluster API) |
|---|---|---|
| Placement | N/A — one host | Scheduling + bin-packing decision, or pay per-PR cluster cost |
| Domain/TLS | One IP-encoded subdomain, no DNS call | Wildcard cert + DNS-01 + cross-namespace secret sync |
| Teardown | Webhook kills a container | Webhook plus a TTL-controller backstop |
| Cost ceiling | Implicit — bounded by host RAM/CPU | Must be explicit — ResourceQuota per namespace, or the autoscaler pays for orphans |
None of those four rows is exotic engineering in isolation — cert-manager, ResourceQuota, and TTL controllers are all well-worn Kubernetes patterns. What makes the multi-node case genuinely harder is that a platform has to get all four right simultaneously before the feature is safe to expose to every PR a user's repository generates, where the single-host version gets three of the four for free just by virtue of only ever having one place for anything to run.
That's the gap between a marketing checkbox — "we support PR previews" — and the operational reality of running that checkbox at fleet scale without a surprise invoice or an accumulating pile of ghost namespaces. It's exactly the kind of infrastructure decision worth making explicit in a self-hosted PaaS roadmap rather than discovering after the first abandoned bot PR leaves a namespace running for a month.
Bex.co is the open-source, AI-native Render alternative built on Cluster API — push a git repo, get a running HTTPS service on machines you own, with the scheduling, TLS, and garbage-collection plumbing this post describes handled for you. Star the repo on GitHub or deploy your first app today.