Skip to content
Move to Tanzahost

Moving from Heroku

Move a Heroku app step by step: Procfile processes to the web and workers, config vars to variables, Postgres to our PostgreSQL, Scheduler to scheduled tasks.

Updated 17 Oct 20266 min read

A Heroku app moves over in six steps: its code from GitHub, its Procfile's lines as the start command and workers, its config vars as variables, its Postgres data as a plain SQL dump, its Scheduler jobs as scheduled tasks, and finally its domain. Your Heroku app keeps serving visitors until the domain points here.

Heroku's commands below come from Heroku's own docs: the CLI commands, import and export for Postgres, config vars, the Procfile, the release phase, Scheduler and custom domains, checked on 2026-10-17.

What goes where

On HerokuOn Tanzahost
The appAn app in a project, built from the same GitHub repository
web: in the ProcfileThe app's web process. We find its start command; set your own under Start command if you need to
worker: and other Procfile linesWorkers, on the app's Processes tab, up to 5 an app
release: in the ProcfileBefore going live, in the app's settings
Config varsThe app's Variables
Heroku PostgresOur PostgreSQL, with pooling
Heroku Key-Value StoreValkey
Heroku SchedulerScheduled tasks, up to 10 an app
Dyno sizesLimits you set, and you pay for what the app uses
Log drainsLog drains, up to 3 a project

1. Add the app from GitHub

  1. Make a project in the location closest to your visitors.

  2. In the Add a service card, choose App, then GitHub, and pick the repository and branch your Heroku app deploys from.

  3. We detect your framework and show what we found, like "We found a Laravel app, from composer.json".

Until GitHub is switched on

Connecting GitHub needs our GitHub app, which isn't set up yet, and the GitHub tab says so. Meanwhile, download your repository as a ZIP and choose Upload a ZIP instead.

If your app needs its database to start, its first deploy may fail. That's expected: deploy again once its variables and database are in (steps 3 and 4).

2. Turn the Procfile into the app's settings

We don't read the Procfile. Take each of its lines in turn:

  1. web: Your app's web process. Leave Start command empty to use the one we found. If yours is special, copy the command into Start command. The app is reached on the port in the PORT variable, as on Heroku, so a command that uses $PORT works as it is. A command that runs a tool only Heroku has, like heroku-php-apache2, can't be copied: leave Start command empty and use ours.

  2. release: Copy its command into Before going live. As on Heroku, it runs once with each new version before visitors see it, and if it fails the version before keeps running.

  3. worker: and any other line, like clock:: on the app's Processes tab, choose Add a worker or scheduled task, pick Worker, give it a name and the line's command. It uses the app's limits, or limits of its own.

3. Copy the config vars

With -s (--shell), Heroku prints them in shell format, NAME=value lines, which the Paste a .env file box reads:

Terminal
heroku config -s -a example-app > heroku.env
  1. Open heroku.env, and delete the lines a database here will set for you: DATABASE_URL and REDIS_URL, and any HEROKU_ ones.

  2. Open your app, then Variables, and choose Paste a .env file.

  3. Paste what's left, and choose Add them.

An app can have up to 200 variables. They're stored encrypted, and the app gets them when it next deploys. Delete heroku.env from your computer afterwards: it holds your secrets.

4. Move the Postgres data

Make the database

In the Add a service card, choose Database, then PostgreSQL. Tick Add its connection details to your app's variables. The app gets DATABASE_URL through the pooler, DATABASE_DIRECT_URL without it, and the DB_ variables Laravel reads.

Make a plain SQL dump

Heroku's backups are in pg_dump's custom format, and our import takes plain SQL, so turn the backup into SQL on your computer with pg_restore:

Terminal
heroku pg:backups:capture -a example-app
heroku pg:backups:download -a example-app
pg_restore --no-owner --no-acl -f example.sql latest.dump
gzip example.sql

heroku pg:backups:download saves latest.dump unless you name another file with -o. pg_restore -f writes the SQL to a file instead of loading it anywhere, and --no-owner --no-acl leaves out Heroku's own users. Use a pg_restore at least as new as the pg_dump that made the backup.

Or dump straight to plain SQL with pg_dump, as Heroku's own docs show:

Terminal
pg_dump -Fp --no-acl --no-owner "$(heroku config:get DATABASE_URL -a example-app)" > example.sql

Import it

Open the new database. Under Import and export, drop example.sql.gz. It can be up to 2 GB, and the page shows the progress and the table being made. If it stops, it shows the line and what the database said. See import and export.

Data that arrives on Heroku after your dump stays there, so take your final dump at a quiet hour, just before you move the domain.

5. Key-Value Store

Add a Cache in the Add a service card: Valkey, made for A cache or for Queues and sessions. Attached to your app, it sets REDIS_URL, REDIS_HOST, REDIS_PORT and REDIS_PASSWORD. Its data doesn't move over: a cache fills up again by itself, and for a queue, let the jobs on Heroku finish first.

6. Scheduler jobs to scheduled tasks

On the app's Processes tab, choose Add a worker or scheduled task, pick Scheduled task, and give it the job's command and a Timetable in cron's five fields. Both Heroku Scheduler and ours use UTC.

Heroku SchedulerTimetable here
Every 10 minutes*/10 * * * *
Every hour at :3030 * * * *
Every day at 00:000 0 * * *

Ours can also run every minute (* * * * *). Each task runs for at most an hour; if one is still running at its next time, that run is skipped. The last 20 runs are kept with their output.

7. Deploy, then check

Deploy the app. Watch the build, then Before going live, then the health check. Once it passes, the app is live on its free address. Check its pages and its logs.

8. Move the domain

List your Heroku domains:

Terminal
heroku domains -a example-app

On your project's Domains tab, choose Add a domain for each. On Heroku a root domain needed an ALIAS or ANAME record; here you can use our nameservers, connect Cloudflare, or add the records we show where your DNS is. When the domain points here, its SSL certificate follows by itself. Then you can turn the Heroku app off.

Example: Asha's shop

Asha's Laravel shop ran on Heroku as duka-shop. Its Procfile:

txt
web: vendor/bin/heroku-php-apache2 public/
worker: php artisan queue:work
release: php artisan migrate --force

In her project duka, in UK-01 · United Kingdom, she added an app from her GitHub repository. We found a Laravel app. She left Start command empty, since heroku-php-apache2 is Heroku's, put php artisan migrate --force in Before going live, and added a worker named queue with php artisan queue:work.

She pasted her config vars without DATABASE_URL, added a PostgreSQL with its connection details for the app, and imported duka.sql.gz made with pg_restore. Her Scheduler job, php artisan schedule:run every 10 minutes, became a scheduled task at * * * * *, as Laravel's scheduler expects. The app went live on duka-7h3k.tanzahost.co, and then she moved duka.co.tz to our nameservers.

What doesn't move

  • Several dynos of one process. Each app runs one web process here.

  • Heroku's add-ons. Connect those services with their own variables, if they let you.

  • The Heroku command line. Our own command-line tool has tanzahost logs --follow, tanzahost vars set and tanzahost deploy, but its download opens only with its first release. Until then, use the dashboard, with its terminal in the browser, or the API.