Push a Ruby app to Dokploy and it might build on Heroku Buildpacks. Push a Node app and it might build on Railpack — unless that build happens to spawn a fresh BuildKit container every single deploy, a bug real enough that Railpack's own maintainer told Dokploy's team their integration "is not recommended for production." Two apps, two entirely different build systems, two entirely different failure modes, one platform.
That's not a hypothetical. Dokploy — one of the more actively developed self-hosted PaaS projects in 2026 — ships four distinct buildpack backends side by side: Nixpacks, Railpack, Heroku Buildpacks, and Paketo Buildpacks, on top of Dockerfile and Compose. Coolify just followed with a Railpack beta of its own in v4.1, layered onto the Nixpacks it already had. The zero-config build layer that used to be "one auto-detect tool, maybe with a Dockerfile escape hatch" is turning into a menu.
For a git-push platform deciding its own build path — bex included — the question isn't whether plurality is possible. Dokploy already proved it is. The question is what it costs to maintain four builder integrations instead of one, what each one actually buys you that the others don't, and whether that trade is worth making before a single real customer repo demands it.
The four backends, side by side
Dokploy's docs list each backend with its own configuration surface, its own environment-variable prefix, and its own maturity level. Laid out together, the differences are bigger than "same job, different implementation":
| Backend | Implementation | Language coverage | Image size (Node.js reference app) | Status |
|---|---|---|---|---|
| Nixpacks | Rust, Nix package manager | Broad — Node, Python, Ruby, Rust, Go, Java, PHP, and more | ~1.3GB | No longer actively maintained upstream; capped at Node 23 |
| Railpack | Go, BuildKit, apt-based | Node.js, Python, Go, PHP, static only | ~450MB (Python drops ~77%) | New, actively developed, but langs still narrow |
| Heroku Buildpacks | Bash + Rust, heroku/builder:24 | Ruby, Python, Java, Clojure, Node.js, Scala, Go, PHP, .NET — no Rust or Elixir | Varies by builder | Stable, CNB-compliant |
| Paketo Buildpacks | Go, composite CNB buildpacks | Java, Node.js, Go, .NET, PHP, Ruby, Python — broadest of the four | Varies by builder | Stable, adds rebasing + SBOM |
Two things jump out immediately. First, Railpack — the backend Dokploy's docs actively steer new users toward as "the successor of Nixpacks" — is also the narrowest. It doesn't build Ruby, Java, or Rust apps at all yet. Second, Nixpacks, the backend with the broadest language coverage, is the one nobody upstream is actively maintaining. A team choosing Dokploy's recommended default is choosing between "broad but stale" and "current but narrow," and the platform doesn't resolve that tension — it just offers both and lets the repo's language pick a side.
What plurality actually costs: three real bugs
The theoretical argument for one build tool over four is obvious — less surface area, fewer integration points, one thing to keep current. The practical argument is sitting in Dokploy's own GitHub issue tracker, and it's more concrete than the abstract case:
- Railpack spawns a new BuildKit container on every build. Dokploy's Railpack integration creates a fresh BuildKit container per deploy instead of reusing one, and Railpack's own maintainer confirmed this usage pattern isn't safe for production — not a Railpack bug, a Dokploy integration bug, specific to how Dokploy wired the fourth backend into its build pipeline.
- A feature silently disappeared crossing Nixpacks → Railpack. Nixpacks exposes a "Publish Directory" field for static-site output; Railpack's builder doesn't support the equivalent, so a user migrating from Nixpacks to the "successor" backend loses functionality with no warning. That's not a bug in either tool — it's a gap that only exists because the platform has to keep two build systems' feature surfaces in sync.
- Build logs report the wrong Railpack version. A small thing on its own, but it's the kind of bug that only shows up when a platform is threading a fast-moving upstream tool (Railpack shipped its own beta in March 2026) through its own version-pinning and logging layer — one more seam, one more place for the two systems' assumptions to drift apart.
None of these are exotic edge cases. They're the ordinary cost of running four parallel integrations against four upstream projects that each ship on their own schedule, with their own config formats (NIXPACKS_* env vars, RAILPACK_* env vars, heroku/builder version pins, Paketo's builder config), and their own detection semantics. Multiply that by however many builders a platform decides to support, and "maintenance surface" stops being an abstraction — it's a specific, growing list of GitHub issues that look exactly like the three above.
It also compounds in a way a single-builder platform never has to think about: staleness skew. Nixpacks — still Dokploy's default for new applications — is no longer actively maintained upstream and tops out at Node 23, while Railpack, the backend Dokploy's own docs point teams toward as the replacement, shipped its own beta only in March 2026 and is still missing languages Nixpacks has supported for years. A platform running one builder has one upstream release cadence to track. A platform running four has four, each aging at a different rate, and "which backend is current" becomes a question a tenant has to answer per repo rather than something the platform can just guarantee.
The Cloud Native Buildpacks spec that Heroku Buildpacks and Paketo both implement actually has a mechanism built for exactly this kind of multi-tool ambiguity: a builder ships an ordered list of buildpack groups, the lifecycle tests each group's detect script in order, and the first group whose required buildpacks all pass is the one used — every other group is simply never invoked. That's a clean way to let one CNB-compliant builder auto-detect across many languages without four separate build systems in play. It's also, notably, not what Dokploy is doing across its four backends: Nixpacks, Railpack, Heroku Buildpacks, and Paketo don't share one detection pass with graceful fallback between them — a tenant explicitly picks which of the four to run, per application, which is what turns "wrong backend for this repo" into a support ticket instead of an automatic retry.
Where plurality actually wins
None of this means one backend is obviously sufficient — the language-coverage gap in the table above is real, not manufactured. A team running a Ruby on Rails app that's been living on Heroku for a decade cannot build it on Railpack today; that backend simply doesn't support Ruby yet. The same is true for Java and Rust. If a platform's only build path is Railpack, those repos don't get a "slower build" — they get a hard failure with no auto-detected fallback.
That's the actual case for plurality, and it's narrower than "more options are always better": a single CNB-family tool (Paketo) already covers Ruby, Java, Go, .NET, PHP, Python, and Node — the same seven-plus languages Dokploy needs two backends (Heroku Buildpacks and Paketo) to cover between them, with meaningful overlap. The real gap Dokploy's four-backend menu closes isn't "Paketo can't do X" — Paketo mostly can. It's Railpack's image-size and cold-build-speed win for the specific languages it does support (Node, Python, Go, PHP), which neither Heroku Buildpacks nor Paketo currently match.
So the honest accounting isn't "four backends, evenly justified." It's: one broad-coverage CNB builder solves the language-support problem completely on its own, and everything past that is chasing a size/speed win on a narrower slice of languages — a genuinely different, and much smaller, trade-off than "we need four build systems to build four kinds of apps."
The call for a Cluster-API-native platform
For a platform like bex — building its own build path on top of a Cluster-API-managed fleet rather than renting someone else's build servers — Dokploy's experience is more useful as a cautionary data point than a template to copy. The three bugs above aren't Dokploy being sloppy; they're what four parallel builder integrations produce structurally, on any team's roadmap.
The more defensible default is the one already implicit in a golden-path bet: Dockerfile first, one CNB-compliant buildpack (Paketo, for its language breadth) as the auto-detect opt-in — not four. Paketo alone already closes the language-coverage gap that would otherwise force a platform to bolt on Heroku Buildpacks or Nixpacks just to build a Ruby or Java repo. Railpack's size and speed advantage is real, but it's a second-order optimization on a subset of languages, not a reason to carry a fourth builder's integration surface, config format, and version-pinning logic before a single tenant repo has actually hit the gap.
That's the difference between "supporting four build systems because we can" and "supporting the one build system that covers the actual language matrix, plus the Dockerfile escape hatch for everything it doesn't." Dokploy shipped the former. The GitHub issues it's now working through are the bill for that choice — one worth watching before a platform decides to match it feature-for-feature.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.
Sources
- Build Type | Dokploy
- Railpack spawns multiple build kit containers · Issue #1493 · Dokploy/dokploy
- "Publish Directory" / static output for railpack · Issue #3476 · Dokploy/dokploy
- Wrong Railpack version in the build logs · Issue #2535 · Dokploy/dokploy
- Railpack vs. Nixpacks: Which Containerization Tool Wins in 2026?
- Buildpacks | Heroku Dev Center
- Paketo Buildpacks · GitHub
- Coolify vs Dokploy: Complete Comparison Guide 2026 | Contabo Blog



