On February 6, 2026, Salesforce SVP Nitin T Bhat announced that Heroku was moving to a "sustaining engineering" model: no new features, no new Enterprise contracts, and a roadmap limited to security patches and stability fixes. In software, that phase has a name — it's the one before end-of-life. Teams that had been putting off a Heroku migration suddenly had a reason to stop putting it off.
So the migration writeups came, and almost all of them landed on the same explanation for why Render, not Railway or Fly.io, kept winning the Heroku cutover: "it supports Heroku buildpacks directly." One widely-shared case study put a number on it — a Rails app moved from Heroku to Render with a render.yml Blueprint that lined up nearly 1:1 with its existing heroku.yml, zero build-pipeline rework, and a bill that dropped from $108/month to $39/month, saving $828 a year.
That's a real result. But "supports Heroku buildpacks directly" is doing more work in that sentence than the evidence backs up — and if you're the one deciding whether your app moves in an afternoon or a quarter, the gap between "buildpack support" and "Heroku buildpack compatibility" is exactly the thing worth checking before you flip DNS.
Two Specs, One Name
Heroku's buildpacks — the classic ones, still running production Cedar-generation apps today — are a specific, narrow contract: three Bash scripts.
bin/detectruns against your app's source and exits 0 if this buildpack applies. Heroku tries each candidate buildpack in order until one claims the app.bin/compiletakes two arguments,BUILD_DIRandCACHE_DIR, and does the actual work — installing a language runtime, resolving dependencies, writing config files, storing anything reusable inCACHE_DIRfor the next build.bin/releaseruns after compilation and prints YAML to stdout describing default config vars and a defaultProcfile— the step that lets a buildpack say "this app should runbundle exec puma" without a human writing that down.
That three-script contract is what thousands of community buildpacks on Heroku Elements and GitHub actually implement: heroku-buildpack-multi (chain several buildpacks), heroku-buildpack-apt (install arbitrary system packages), heroku-buildpack-nginx, and dozens of language forks that patch the official Ruby, Node, or Python buildpacks for some specific edge case. If your Heroku app's .buildpacks file references one of these, that script triplet is your actual build pipeline.
Cloud Native Buildpacks (CNB) — the spec underneath Render's, Railway-adjacent, and bex's own build steps — looks similar at a glance and is not the same contract. CNB buildpacks implement bin/detect and bin/build; there's no separate bin/release phase, and instead of writing files into a slug, a CNB buildpack contributes OCI image layers directly, with build-time and launch-time metadata tracked in TOML rather than a Procfile. The two specs are close enough that Heroku itself had to publish a dedicated porting guide — "Creating Cloud Native Buildpacks from Classic Buildpacks" — to walk its own team through moving its own official buildpacks from one contract to the other. If Heroku couldn't treat its own classic buildpacks as automatically CNB-compatible, no platform claiming generic "buildpack support" is running your .buildpacks file unmodified either.
Railway complicates the picture further: its Nixpacks tool isn't a buildpack implementation of either spec. It inspects your source, generates a Nix expression and a synthesized Dockerfile, and builds that — a different mechanism that happens to produce a similar zero-config experience for standard frameworks, via a completely different pipeline underneath.
Fly.io, meanwhile, actually does support real CNB buildpacks (via Paketo builders and its own pack-CLI-driven builder type) alongside Dockerfile and prebuilt-image builds — but its own docs actively discourage the option: "Don't use buildpacks if you don't have to; they're brittle, bloated, and prone to change." Fly ships the same spec Render does and steers users away from it in favor of Dockerfiles. The "Fly expects a Dockerfile" framing that shows up in migration writeups is a support-and-defaults choice, not a missing capability.
| Platform | What actually runs your build | Same contract as Heroku classic? |
|---|---|---|
| Heroku (Cedar) | Classic buildpacks: bin/detect → bin/compile(BUILD_DIR, CACHE_DIR) → bin/release (YAML) | — (the original) |
| Render | Cloud Native Buildpacks (CNB): bin/detect → bin/build, OCI layers + TOML | No — a different, later spec |
| Fly.io | CNB via Paketo builders (discouraged default; Dockerfile preferred) | No — same spec as Render, opt-in |
| Railway | Nixpacks: source → generated Nix expr + synthesized Dockerfile | No — not a buildpack spec at all |
| Dokku | Herokuish: runs classic bin/detect/bin/compile/bin/release unmodified inside a wrapper container | Yes — literal script execution |
Only one row in that table is actually running Heroku's buildpack scripts as written.
What "Buildpack Support" Gets You in Practice
Render's own community forum bears this out. Threads there show official-framework buildpacks (Ruby, Node, Python) working through Render's auto-detection without friction — the same 80% case Cloud Native Buildpacks was built to cover, where a repo's shape ("there's a package.json," "there's a Gemfile") is enough to pick a builder and go. But when a user tries to bring a non-official Heroku buildpack — one of those community forks, or a .buildpacks file listing something outside Render's own detected set — the documented answer is to email support@render.com for manual help. Render's own migration docs flag multi-buildpack Heroku apps as a case still being worked out, not a solved path.
That's the tell. Render's "buildpack support" is CNB-based framework auto-detection, applied broadly enough that it matches Heroku's classic-buildpack behavior for the common cases. It is not literal execution of the .buildpacks file your Heroku app has been running against for the last five years. For the Rails app in the $828/year case study — Puma, standard gems, no exotic build customization — that distinction never surfaces, because CNB's Ruby detection produces the same runnable app a classic buildpack would have. For a team running heroku-buildpack-apt to install a system library, or heroku-buildpack-multi to chain three buildpacks in sequence, it surfaces immediately, as a support ticket instead of a DNS change.
Dokku is the one platform on this list that closes that gap for real, and it does it by not reimplementing anything. Herokuish, the layer underneath Dokku's buildpack support, runs the exact three-script contract — bin/detect, bin/compile, bin/release — inside a container wrapper, unmodified. Point a Dokku app at the same .buildpacks file your Heroku app used, including a community buildpack Heroku itself never officially blessed, and it runs the same scripts, in the same order, expecting the same BUILD_DIR/CACHE_DIR arguments. The tradeoff is that herokuish is a much smaller, less actively maintained project than the CNB ecosystem — genuine compatibility, on infrastructure with a thinner support surface than Render's.
What Closing the Gap Would Actually Take
bex's own build pipeline sits in the same place Render's does: clone the repo, run Cloud Native Buildpacks via pack and Paketo builders, push the resulting OCI image to the registry. That's the right default — it's a maintained, standards-track spec with an active ecosystem, and it's why bex can offer the same zero-config "push a git repo, get a running app" experience Render does for the 80% of apps that fit a standard framework shape.
Matching Heroku's literal buildpack API, for the tail of apps that don't fit that shape, is a separate and specific engineering task, not a checkbox next to "we support buildpacks":
- Fetch and order buildpacks from a
.buildpacksfile orBUILDPACK_URL. A classic Heroku app declares its buildpack chain explicitly; a platform has to resolve and clone each one before build, in the declared order. - Run
bin/detectper candidate, honoring exit codes. Not "does this repo look like Ruby" — literally execute the buildpack's own detect script and respect its exit status, since community buildpacks encode detection logic a generic framework sniff won't replicate. - Execute
bin/compilewith realBUILD_DIR/CACHE_DIRsemantics. The cache directory has to persist and be handed back on the next build unmodified — the exact behaviorheroku-buildpack-aptand friends depend on to avoid re-downloading packages every deploy. - Parse
bin/release's YAML into your own process model. DefaultProcfileentries and config vars coming out of a script's stdout, not a platform-native manifest — the step that turns compile output into "here's the command that starts your web process." - Package the result into your existing image/registry pipeline so it deploys the same way a CNB-built image does downstream — the classic buildpack contract predates OCI images entirely, so this step is pure integration work with no upstream spec to lean on.
None of that is exotic engineering. It's the herokuish approach, reimplemented or vendored, sitting alongside an existing CNB build step rather than replacing it. The honest scope, though, is narrow: it only pays off for the specific subset of migrating apps that depend on non-official or custom classic buildpacks — a smaller slice than the marketing framing of "Heroku buildpack compatible" implies, but a real one. It's the exact slice that turns a documented $828/year DNS-change migration into an open support ticket if a platform hasn't built it.
Before You Read "Supports Buildpacks" as "My App Just Works"
If you're planning a Heroku cutover on the strength of a target platform's buildpack story, three questions settle whether that story applies to you:
- Does your
.buildpacksfile list only Heroku's official Ruby/Node/Python/Go/PHP/Java buildpacks? If so, CNB-based auto-detection on Render, Fly, or bex will very likely reproduce the same runnable app — that's the case the $828/year migration and most "afternoon" migration stories describe. - Does it list a community or custom buildpack —
heroku-buildpack-multi,heroku-buildpack-apt, an internal fork, anything not in Heroku's officially maintained set? If so, "supports buildpacks" on a CNB-based platform means you re-derive that behavior as a Dockerfile or a custom CNB buildpack — budget for that as a real migration task, not a formality. - Do you need the literal script to run unmodified, right now, with no rewrite? Herokuish-based platforms like Dokku are the only ones on this list that offer that today; everyone else, bex included, is CNB-first with a Dockerfile escape hatch for what CNB's auto-detection doesn't cover.
Heroku's sustaining-engineering mode doesn't force anyone's hand overnight. But it does mean the buildpack question is worth answering with the actual API your app depends on, not the word "buildpacks" appearing on two platforms' marketing pages.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own via the same Cloud Native Buildpacks pipeline described above, with a Dockerfile fallback for anything that doesn't fit. Star the repo on GitHub or deploy your first app today.



