Skip to main content
HostwaresHostwares

Migrating from Heroku to Hostwares: A Complete Guide (2026)

Hostwares Team··12 min read

Before you start: is moving actually right for you?

Heroku is not a bad platform. If you depend on Shield or Private Spaces for compliance, or on a specific add-on with no equivalent elsewhere, the migration below will cost you more than you save. Stay put.

The case for moving is narrower and mostly about memory. A Standard-1X dyno is $25/mo for 0.5 GB of RAM, the same memory as a $7 Basic dyno. Standard-2X doubles it to 1 GB for $50. If you are paying Standard prices for an app that needs more memory than compute, that is where the money goes. Prices verified 13 August 2026 from heroku.com/pricing.

Budget an hour for a simple app, half a day for one with several add-ons and a large database. Nothing below requires downtime until the DNS step, and that step is designed to avoid it.

Step 1 — Take an inventory

Run these against your existing app so you know exactly what you are recreating:

heroku apps:info -a your-app
heroku addons -a your-app
heroku config -a your-app
heroku ps -a your-app
heroku domains -a your-app

heroku ps matters more than people expect. If you run a worker or clock process alongside web, each one becomes its own deployment here, not a single container.

Step 2 — Export your config vars

Get them in a form you can paste rather than retyping:

heroku config -a your-app --shell > .env.heroku

That produces KEY=value lines. Two things to fix before importing:

  • Drop DATABASE_URL. It points at Heroku Postgres and will be replaced. Leaving it in is the single most common way people end up with a live app still writing to their old database after cutover.
  • Drop add-on-injected vars for anything you are not carrying across — REDIS_URL, CLOUDINARY_URL, SENDGRID_API_KEY and similar. Recreate them pointing at the new services.

Everything else — API keys, secrets, feature flags — moves across unchanged.

Step 3 — Capture and restore the database

This is the part worth doing carefully. Take a fresh backup and download it:

heroku pg:backups:capture -a your-app
heroku pg:backups:download -a your-app   # writes latest.dump

latest.dump is a PostgreSQL custom-format dump, so restore it with pg_restore rather than psql:

pg_restore --verbose --clean --no-acl --no-owner \
  -d "postgres://user:pass@host:5432/dbname" latest.dump

--no-acl --no-owner is not optional. The dump carries Heroku's role names, and without those flags the restore fails on every GRANT and ALTER OWNER statement referencing a role that does not exist on the target.

Verify before moving on. A restore that "looked fine" but silently skipped a table is worse than one that failed loudly:

psql "$NEW_DATABASE_URL" -c "\dt"
psql "$NEW_DATABASE_URL" -c "SELECT count(*) FROM your_busiest_table;"

Compare that count against the same query on Heroku. If your app is actively taking writes, expect a small difference — you will close that gap in step 7.

Step 4 — Map your add-ons

Most Heroku add-ons have a direct equivalent, either as a managed database or from the one-click catalogue.

Heroku add-onEquivalent
Heroku PostgresManaged PostgreSQL
Heroku Key-Value Store / RedisManaged Redis
JawsDB / ClearDB MySQLManaged MySQL or MariaDB
Papertrail, LogentriesContainer logs, or self-hosted Grafana Loki
Heroku SchedulerScheduled tasks on the deployment
SendGrid, MailgunKeep them — they are independent services, just move the API key
New Relic, Scout APMKeep them, or self-host Grafana and Prometheus

Third-party SaaS add-ons billed through Heroku (SendGrid, Twilio, Stripe) are not really Heroku features. Create an account directly with the vendor, move the API key, and remove the add-on. You often pay less buying direct.

Step 5 — Buildpacks and the Procfile

This is where a migration is most likely to surprise you, so read it before you start rather than after.

Heroku builds with buildpacks and starts processes from a Procfile. Hostwares builds from a detected framework or a Dockerfile. For the large majority of apps — Node, Python, Ruby, Go, PHP, static sites — detection handles it and you do not think about buildpacks at all.

Where you do need to think:

  • Your start command lives in the Procfile. Copy it into the deployment's start command. web: gunicorn app:application becomes a start command of gunicorn app:application.
  • Extra process types become extra deployments. A worker: celery -A tasks worker line is a second deployment from the same repository with a different start command, not a setting on the first.
  • Custom or community buildpacks do not carry over. If you rely on one — heroku-buildpack-apt, a headless Chrome buildpack, anything installing system packages — write a Dockerfile instead. That is the honest answer, and it is usually a ten-line file.
  • Check your language version pin. A runtime.txt, .python-version or the engines field in package.json is respected by detection, but verify it built against the version you expected before cutting over.

If your app is a plain web: process on an official buildpack, none of this applies and you can move on.

Step 6 — Deploy and test on the temporary domain

Connect the same GitHub repository, import the cleaned .env.heroku, point DATABASE_URL at the restored database, and deploy. Every new deployment gets a working subdomain with SSL, so you can exercise the app properly while the old one is still serving live traffic.

Test the paths that break quietly:

  • Anything writing to the database — confirm it lands in the new one, not the old
  • Background jobs, if you moved a worker process
  • File uploads, if you were using ephemeral dyno storage (that data does not survive a Heroku restart either, which is worth knowing)
  • Outbound email and webhooks — check the vendor received them
  • Anything with an IP allowlist. A new host means a new outbound IP.

Step 7 — Cut over DNS without downtime

The order here is what avoids a gap.

A day before: drop your DNS TTL to 300 seconds. If it is currently 3600 or 86400, this is the step that decides whether cutover takes five minutes or a day, and it has to be done in advance to take effect.

At cutover:

  1. Put the Heroku app into maintenance mode if you cannot tolerate split writes: heroku maintenance:on -a your-app
  2. Take a final incremental database sync — repeat step 3 against the new database, or replay the delta if your data model allows it
  3. Add your custom domain to the new deployment and let the certificate issue
  4. Update the DNS record to point at the new host
  5. Watch both sets of logs. Traffic drains from Heroku over one TTL.

Do not delete the Heroku app the same day. Leave it running for a week. It costs one more month at most and it is the only rollback you have.

For a truly zero-downtime cutover on a write-heavy app, the usual approach is to make the application write to both databases for a short window, or accept a two-minute maintenance window during a quiet hour. Anyone promising zero downtime with no dual-write and no maintenance window on a stateful app is skipping over the hard part.

Step 8 — After cutover

  • Confirm backups are running on the new database, and test a restore. A backup you have never restored is a hypothesis.
  • Re-point monitoring and uptime checks
  • Update any IP allowlists at third parties
  • Cancel Heroku add-ons before the app itself — add-ons bill separately and outlive a deleted app in some cases
  • Only then remove the Heroku app

What this actually saves

A representative case: one Standard-1X dyno at $25/mo with Heroku Postgres Essential-1 at $9/mo is $34/mo, for 0.5 GB of application memory. The equivalent here is a Standard pack at $7/mo with 2 GB, plus a managed database pack at $3/mo — $10/mo, with four times the memory.

Work out your own figure with the Heroku cost calculator, which uses Heroku's published rates rather than an estimate.

Whether that is worth a morning of work depends on your bill. At $34/mo it pays back in a month. If you are on an Eco dyno at $5, it does not — and you should stay where you are.

Related

Ready to deploy?

Start free on Hostwares - your app live in 60 seconds.

Get Started Free