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 Heroku | On Tanzahost |
|---|---|
| The app | An app in a project, built from the same GitHub repository |
web: in the Procfile | The app's web process. We find its start command; set your own under Start command if you need to |
worker: and other Procfile lines | Workers, on the app's Processes tab, up to 5 an app |
release: in the Procfile | Before going live, in the app's settings |
| Config vars | The app's Variables |
| Heroku Postgres | Our PostgreSQL, with pooling |
| Heroku Key-Value Store | Valkey |
| Heroku Scheduler | Scheduled tasks, up to 10 an app |
| Dyno sizes | Limits you set, and you pay for what the app uses |
| Log drains | Log drains, up to 3 a project |
1. Add the app from GitHub
Make a project in the location closest to your visitors.
In the Add a service card, choose App, then GitHub, and pick the repository and branch your Heroku app deploys from.
We detect your framework and show what we found, like "We found a Laravel app, from composer.json".
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:
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 thePORTvariable, as on Heroku, so a command that uses$PORTworks as it is. A command that runs a tool only Heroku has, likeheroku-php-apache2, can't be copied: leave Start command empty and use ours.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.worker:and any other line, likeclock:: 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:
heroku config -s -a example-app > heroku.envOpen
heroku.env, and delete the lines a database here will set for you:DATABASE_URLandREDIS_URL, and anyHEROKU_ones.Open your app, then Variables, and choose Paste a .env file.
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:
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.sqlheroku 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:
pg_dump -Fp --no-acl --no-owner "$(heroku config:get DATABASE_URL -a example-app)" > example.sqlImport 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 Scheduler | Timetable here |
|---|---|
| Every 10 minutes | */10 * * * * |
| Every hour at :30 | 30 * * * * |
| Every day at 00:00 | 0 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:
heroku domains -a example-appOn 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:
web: vendor/bin/heroku-php-apache2 public/
worker: php artisan queue:work
release: php artisan migrate --forceIn 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 setandtanzahost deploy, but its download opens only with its first release. Until then, use the dashboard, with its terminal in the browser, or the API.