Migration · Heroku

Migrate from Heroku to KERNSTACK

Migrating from Heroku to KERNSTACK means translating a Procfile and a set of add-ons into one declared project: web and worker processes become container workers, scheduled jobs become workflows, Postgres data becomes key-value tables, and config vars become project secrets — all in one stack.json on European bare metal. Buildpacks and dyno semantics do not move. The code, the data, and the domain do.

01

Transcribe the Procfile and add-ons

List every process type and every add-on with its plan. Mark the language of each process — nodejs, python, and php map to workers; anything else is a rewrite conversation to have now.

02

Declare the project in stack.json

Processes become workers, Scheduler jobs become workflows, Postgres tables become keyed tables, and config vars become secrets. Review the kernstack plan as a team before deploying.

03

Move the data and rehearse

Export Heroku Postgres with pg_dump, reshape rows to the declared keys, and load into tables on a preview target. Copy stored files into buckets and run the full rehearsal against production-shaped data.

04

Move the domain, close the add-ons

Point the custom domain at the KERNSTACK gateway once the preview passes. Cancel add-ons one by one as replacements prove out, and scale the dyno formation down last.

Reading a Heroku app as a stack.json project

A Heroku app is two lists: the Procfile’s process types and the add-ons page. The migration reads both as a declaration draft. Web and worker processes become KERNSTACK workers with the runtime that matches the code — nodejs, python, or php. One-off scheduled jobs from Scheduler become workflows whose steps call those workers. The release phase and one-off dynos translate to steps you run during deployment rehearsals, not to standing resources.

The add-ons decide the data work. Heroku Postgres exports with pg_dump; rows then face the usual question — which access is keyed, and which is genuinely relational? Keyed data loads into tables. Relational remainders keep a Postgres you operate or rent elsewhere and call over the network; KERNSTACK has no SQL layer waiting to surprise you later, which is exactly why the question belongs at the start.

  • Config vars are re-entered as project secrets. Their values never travel through an export tool.
  • Redis-style add-ons used as job queues become workflows and workers; used as caches, they become a design question, not a resource mapping.
  • Ruby is not a KERNSTACK worker runtime. A Rails app needs a rewrite of its server shape or stays on Heroku — stated here so nobody discovers it in week two.

Cutover is a domain change, rehearsed on a preview

Heroku made deploys calm, and the migration should keep that temperament. Stand the project up on a preview target, run the real data export into it, and exercise the flows your users exercise — sign-in, the paying path, the background jobs firing on schedule. Only then move the custom domain. Heroku keeps serving until DNS and traffic say otherwise, so the old app is its own rollback plan.

After cutover, the add-ons page becomes a closing checklist: cancel each add-on as its replacement proves out, and let the dyno formation scale to zero last. The final Heroku bill arriving smaller than the previous one is the migration’s most persuasive status report.

Resource-by-Resource Mapping from Heroku

  • Web dynos (Node.js, Python, PHP) → workers: The web process becomes a container worker with the matching runtime, declared in stack.json and metered at 0.01 credits per GiB-second of compute.
  • Worker dynos and background jobs → workers + workflows: Always-on job processes stay workers; the ordering and triggering around them becomes declared workflow steps, including human steps where a person used to intervene.
  • Heroku Scheduler → workflows: Cron-style jobs become scheduled workflows. The ten-minute execution habit of Scheduler disappears with the dyno model itself.
  • Heroku Postgres → tables: pg_dump out, reshape to keys, load. Genuinely relational datasets keep an external Postgres and are called over the network — there is no SQL target being hidden from you.
  • File storage add-ons (S3-backed) → buckets: Objects copy over HTTPS; uploads switch to presigned URLs issued by your workers.
  • Config vars → secrets: Re-entered once as project secrets and injected into workers and functions. Rotation afterwards is a project operation, not a dashboard visit.
  • Buildpacks and the slug model → declared runtimes: Buildpacks do not translate. The runtime field on each worker — nodejs, python, php, or a Go binary — is the whole build story afterwards.
  • Ruby and other runtimes → no direct equivalent: Rails and other unsupported runtimes need a server rewrite or stay on Heroku. The frontend and data of such an app can still move; the process cannot.

Frequently asked questions

Different mechanism, similar temperament: change the code or stack.json, read the plan, promote. What you gain over the Procfile world is that the data, jobs, and search around the process are in the same declaration — what you lose is the buildpack ecosystem’s fifteen years of habit.

Dynos bill for existing; KERNSTACK bills for working. Compute is 0.01 credits per GiB-second, storage 0.04 credits per GB-hour, and an idle project spends no compute credits. The portfolio of add-on invoice lines collapses into one credit balance with the project’s name on it.

With clear eyes: Ruby is not among the worker runtimes — nodejs, python, php, and Go binaries are. Rails teams either rewrite the server in a supported runtime, which is a product decision, or keep Heroku and migrate nothing. This guide would rather lose the migration than misdescribe it.

Related capabilities, architectures, and comparisons

Ship your stack on the Software Lifecycle Cloud.

Closed preview on dedicated European infrastructure. Bring a repository or start from a blueprint.