Ask a Heroku refugee why they picked Render and you will often hear "it just took my buildpacks." Here is the uncomfortable part: Render's native runtimes never executed a single Heroku buildpack. What Render built was a migration machine that absorbed your Heroku-isms — Procfile, release phase, config vars, buildpack list — so thoroughly you never noticed the buildpacks were running inside a generated Dockerfile, not on the platform.
That distinction matters now. Heroku itself started offering Cloud Native Buildpacks on Cedar on September 3, 2026. And if your next move is onto a Cluster API fleet on machines you own, nobody absorbs anything for you. Rebuilding that compatibility is your job, and the bill comes due in engineering hours, not compute.
Verdict up front, receipts below. To replicate Heroku-compatible, zero-Dockerfile deploys on a self-hosted fleet, you pick one of two builders: Paketo (Cloud Native Buildpacks, via pack or kpack) or the Nixpacks lineage. Paketo costs more to stand up and less to live with; Nixpacks costs almost nothing to start and quietly mortgages your future on an unmaintained builder:
| Replication cost | Paketo (CNB) | Nixpacks |
|---|---|---|
| Time to first built image | An afternoon (pack + builder pull) | Under an hour (single binary) |
| Time to fleet-wide build service | 1–3 days (kpack, registry, builder order) | 1–2 days (CI wiring, registry push, per-app config) |
| Moving parts you now operate | kpack controller, builder image, registry, stack-update automation | CI build job, registry, one nixpacks.toml per snowflake app |
| Heroku Procfile honored? | Yes, via the Procfile buildpack (web process becomes default) | No — start command is re-derived by providers or set by hand |
| Custom apt/ffmpeg-style buildpack equivalent | Weak — no official apt story; extend the builder or write a buildpack | Strong — one nixPkgs line in most cases |
| Heroku release phase equivalent | None built in — becomes a pre-deploy job you wire yourself | None built in — same pre-deploy job |
| Maintenance outlook, Sept 2026 | Active (lifecycle + pack shipped releases in March 2026) | Maintenance mode — Railway's successor is Railpack (see callout) |
If that table answers your question, the checklist at the end is your off-ramp. For how Render pulled off the trick and what each row takes on a CAPH fleet — read on.
What Render's "buildpack compatibility" actually was
Rewind to 2022, peak Heroku exodus. Render's migration story was a Heroku CLI plugin generating a Dockerfile.render, a render.yaml, and a .render-buildpacks.json manifest into your repo. The Dockerfile ran your build through Heroku's own buildpacks — official and custom alike — inside Render's Docker runtime, using ghcr.io/renderinc/heroku-app-runner images.
A Render engineer put it plainly on the community forum: Render doesn't directly manage buildpack behavior; it imports buildpacks from Heroku in order to duplicate their functionality. The moat was never a buildpack runtime. It was a code generator plus runner images that kept your Heroku build working, byte for byte, while Render owned the scheduler, the router, and the TLS.
That defines the actual surface you must rebuild. It was never "implement the Heroku slug compiler." It was three things: run the buildpack interface (detect, compile, release) against the app source; translate the Procfile into runnable processes; and preserve the release-then-run ordering Heroku apps assume. Everything else — config vars, add-on URLs, log drains — is environment plumbing, not build plumbing.
Now look at Render's current Heroku migration guide. The buildpack plugin is gone from the docs. Today's flow is: catalog your Procfile processes, then recreate each one as a Render service by pasting commands into a dashboard — build command, pre-deploy command (your old release process), start command (your old web process).
Render even publishes the concept mapping as a table: dyno becomes instance, release phase becomes pre-deploy command, preboot becomes zero-downtime deploys. The moat moved. Render no longer duplicates your buildpacks; it absorbs your Procfile semantics so completely that migration needs no Dockerfile at all.
So the honest replication target for a self-hosted platform is the union of both eras: zero-Dockerfile builds from git push, Procfile-shaped process management, and a release-phase equivalent. The rest of this post prices exactly that.
Heroku crosses over too: the urgency box
While Render was absorbing Heroku-isms, Heroku was quietly building their replacement. Effective September 3, 2026, Cloud Native Buildpacks work on Cedar-generation apps: heroku apps:create <name> --stack cnb or heroku stack:set cnb moves an app off classic buildpacks onto CNB, which emits a standard OCI image (or a slug-compatible OCI layout) instead of a slug. Fir-generation apps already defaulted to the cnb stack; Cedar keeps classic buildpacks as the default for now, and Heroku publishes a classic-vs-CNB comparison plus a migration guide for crossing over.
Why this changes your urgency math: Heroku now maintains a pack-compatible build path for every major language (heroku/builder:24 today, :26 already visible; the Node.js CNB alone covers npm, pnpm, and yarn). Every month on classic buildpacks, you drift from the interface Heroku itself is investing in — and from the interface your self-hosted future speaks. Migrating Heroku-to-Heroku-CNB first is the cheapest rehearsal for migrating Heroku-to-your-own-fleet later: same OCI output, same process model, zero new infrastructure.
Same apps, two builders
Take two archetypes straight from real Heroku fleets and run each through both builders.
Archetype A is the typical app: a Node API with a Procfile. One web process, one worker process, and a release process running migrations. No native dependencies, no custom buildpacks. This is the median Heroku app and the one every zero-config builder is optimized to flatter.
With Paketo, the build is one command against a versioned builder:
pack build my-api --builder paketobuildpacks/builder-jammy-baseDetection picks Node, installs dependencies with the detected package manager, and the Procfile buildpack reads your Procfile: web becomes the image's default process, worker is preserved as a named process type you can launch with --entrypoint overrides or separate workload definitions. Pin the builder tag and the Node version (BP_NODE_VERSION or .nvmrc support via the node-engine buildpack), and the build is reproducible enough to diff. If you want maximum Heroku familiarity instead of Paketo's opinions, swap the builder: pack build my-api --builder heroku/builder:24 runs Heroku's own CNBs and honors project.toml the way Fir apps do.
With Nixpacks, the build is equally short:
nixpacks plan . > /dev/null && nixpacks build . --name my-apiplan shows the detected providers, install command, build command, and start command. There is no Procfile parsing — that is the first real behavioral fork. Nixpacks re-derives your start command from its providers; your worker process type and release phase do not survive the trip.
You recover them as platform configuration: a second workload definition for the worker, a pre-deploy hook for migrations. For Archetype A that is ten minutes of YAML. Both builders clear the typical app easily; Paketo keeps more of your Heroku semantics, Nixpacks asks fewer questions.
Archetype B is the app that keeps platform engineers up at night: a Python service with a native dependency. On Heroku it carries two buildpacks: heroku/python plus a custom apt buildpack installing ffmpeg and friends. This is where the "zero-config" brochures stop and the replication bill starts.
Nixpacks shines here, and honesty requires saying so. The whole custom buildpack collapses into one config stanza:
[phases.setup]
nixPkgs = ["ffmpeg", "libsm6"]One file, no new infrastructure, and the nix package set covers the long tail of system libraries that custom Heroku buildpacks usually exist to provide. This is the Nixpacks lineage's genuine structural advantage: the package universe is the config surface.
Paketo's answer is weaker and you should budget for it. There is no official apt-install story in the Paketo order groups. Your options are extending the builder image with the OS packages you need, writing a small custom buildpack, or restructuring the app to vendor the dependency.
None of these is exotic — kpack's ClusterStore/ClusterBuilder model expects you to curate builders — but each is a half-day to two days of platform work where Nixpacks needed a config line. If your fleet's Heroku estate is heavy on apt-style custom buildpacks, this row alone can flip the verdict.
Where the Node result does not generalize: polyglot repos (Paketo's builder order groups beat Nixpacks' provider chain), monorepos (both need subpath scoping — budget an hour per extra service), and strict reproducibility (pinned builder digests beat re-resolved nixpkgs unless you pin the revision too).
Maintenance-mode callout. Nixpacks is in maintenance mode; Railway's successor is Railpack, a Go rewrite on BuildKit with no intermediate Dockerfiles, mise-managed toolchains, and
railpack.jsonconfig. Everything in the Nixpacks column describes the lineage's approach; for any new adoption, evaluate Railpack as the maintained pick inside it.
What replication actually costs on a CAPH fleet
Here is the quantified version for a Cluster API fleet on Hetzner (CAPH), where you own the machines, the registry, and the consequences. Ranges are order-of-magnitude for a team that already runs the cluster — no cluster-bootstrap cost included.
| Cost line | Paketo via kpack | Nixpacks in CI |
|---|---|---|
| Setup steps | 6–8: install kpack release, registry secret + service account, ClusterStore, ClusterStack, ClusterBuilder, first Image resource, git-trigger wiring, cache config | 4–6: install binary, plan review per app, CI build-and-push job, registry auth, deploy manifests, per-app config files |
| Hands-on time | 1–3 days (first builder curation is the long pole) | 1–2 days (per-app config drift is the long tail) |
| Moving parts | kpack controller, builder image, OCI registry, stack-update automation | CI runner with Docker/BuildKit, OCI registry, N config files |
| Build compute | Build pods on worker nodes; size one node with fast local disk for layer cache, or registry-backed cache | CI runners (same sizing logic); nix store download on cold cache is the slow path |
| Cache story | CNB layer rebasing: unchanged layers are reused without rebuilding; registry cache is first-class | Nixpacks layer caching exists but is coarser; cold builds re-resolve nixpkgs unless pinned |
| Ongoing maintenance | Track stack + builder releases; test builder bumps against a canary app monthly | Track provider behavior changes per app; each new language version is a per-app verification |
Sensitivity — where your bill lands inside those ranges:
- Language count. One language: both paths sit at the low end. Five languages: Paketo adds buildpacks to one builder order (centralized, tested once); Nixpacks re-verifies five provider chains (distributed, tested per app). Centralized curation wins past two or three runtimes.
- Custom buildpacks. Each apt/ffmpeg-style custom buildpack is a config line on Nixpacks and a builder extension or custom buildpack on Paketo (half-day to two days each, reusable). A fleet with more than a couple of these should price Paketo honestly before committing.
- Monorepo vs many repos. kpack wants one Image resource per buildable unit — cheap to template, but each is API surface you own. Nixpacks wants config per directory. Roughly a wash; budget an hour per service either way.
- Compliance and provenance. If you need SBOMs, signed images, or reproducible builds for auditors, Paketo's ecosystem (Syft buildpack, pinned builders, OCI-native output) starts ahead. Nixpacks can get there via CI plugins, but that is more wiring you own.
And the honest gap list — Heroku behaviors neither builder replicates, which become platform features you build:
- Release phase. Heroku runs
releasebefore every deploy swaps traffic. CNB and Nixpacks both end at "here is an image." Your replication is a pre-deploy Job or pipeline step that runs migrations and gates the rollout. Budget a day to build it right (idempotent migrations, failure rollback, per-service opt-out), because every Heroku app with areleaseprocess assumes it exists. - Procfile process types as a unit. Heroku scales
webandworkerindependently from one artifact. Paketo preserves process types in the image; Nixpacks does not. Either way, the fan-out from one image to N independently scaled workloads is your deployment layer's job (K8s Deployments per process type, a small controller, or render.yaml-style IaC you define). - Build-time config parity. Heroku's buildpack API exposes config vars at build time in controlled ways; CNB has build-time env scoping, Nixpacks inherits CI env. Audit which of your apps read secrets at build time before you move them — the failure mode is a green build that bakes in a missing variable and a red deploy two steps later.
The shape of the answer: Paketo front-loads platform work and amortizes it across every app; Nixpacks back-loads per-app work that grows with fleet diversity. One or two runtimes with heavy native dependencies, and the Nixpacks lineage can stay cheaper for a long time. Several languages with Procfiles and release phases everywhere — the typical Heroku estate — and Paketo's centralized builder pays back its setup cost within the first few migrations.
Your Heroku off-ramp: decision rules and a checklist
Three rules, each one line. Choose Paketo when your apps are Procfile-shaped and polyglot enough that one curated builder beats per-app config. Choose the Nixpacks lineage (evaluating Railpack as the maintained pick) when native dependencies dominate and your language count stays small. Choose a Dockerfile when a single app's build is weirder than your whole builder strategy — escape hatches are platform features, not failures.
Then run the checklist:
- Inventory the Heroku-isms. Every app's buildpacks (official vs custom), Procfile process types, and
releasecommands. Custom buildpacks and release phases are the cost drivers, not language count alone. - Rehearse on Heroku's CNB first. Moving a Cedar app to
--stack cnbvalidates your Procfile andproject.tomlagainst OCI output before you own any infrastructure. - Stand up the builder before the first migration. kpack + registry + curated builder, or CI + registry + Nixpacks/Railpack — cache configured, canary app green. Migrating onto an unproven build path doubles every debugging session.
- Build the release-phase equivalent once. A pre-deploy migration Job with rollback semantics, reused by every service. Before app three, not after app ten.
- Migrate Archetype A first, price Archetype B honestly. Typical apps validate the pipeline; each native-dependency app gets its own estimate against the sensitivity rows above.
The deeper story is a changing of the guard. Classic Heroku buildpacks were a vendor interface: one company defined detect, compile, and release, and everyone else adapted. Cloud Native Buildpacks are a standard interface — Heroku, Paketo, Google, and a dozen platforms build to the same pack contract, and the artifact is a plain OCI image you can run anywhere.
Render's great trick was making the vendor interface portable for one migration hop. Rebuilding that trick for yourself, on the standard interface, is the last migration of its kind you will ever have to do.
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.



