All posts

Migrating from Heroku to Coolify

Ross Hill · August 27, 2026

Heroku made git push deploys normal. A generation of developers learned to ship on it, and the workflow it invented is still the one most platforms copy. If you're reading this, the platform probably isn't the problem. Something else changed: the bill, the resource ceiling, or where your data lives.

Coolify is one of the places people land. It's an open-source deployment platform that runs on a server you control, with the same connect-a-repo-and-deploy model. This guide covers the concept mapping, the migration sequence, and the parts Heroku was doing for you that you'll now own.

Why people migrate

Cost at scale. Heroku bills per resource. A Basic dyno runs $7 USD/month for an always-on container, and Eco dynos share a pool of 1,000 dyno hours for $5 USD/month across your account, sleeping after a period of inactivity. Databases and add-ons are billed separately on top. None of that is unreasonable on its own. It's the addition that gets people: a web process, a worker, a database, a scheduler, a log drain, then the same again for staging. Check Heroku's pricing page for current figures before you model anything.

Dedicated resources. Dynos are containers sized by tier. If you want more memory or CPU, you move up a tier. On Coolify, you're deploying onto a server whose specs you picked, and every app and database on it shares those resources on your terms.

Data residency. Heroku's self-serve regions are US and EU. A Canadian region exists through Private Spaces, at a price point that rules it out for most teams. If your data needs to stay in Canada, that's a hard constraint rather than a preference. Our full Heroku comparison breaks down the side by side.

Concept mapping

Most of what you know transfers. The names change.

HerokuCoolify
AppApplication resource inside a project
DynoContainer running your application
Procfile process typesOne application per process, or a Docker Compose stack defining each service
BuildpacksNixpacks (auto-detects your stack) or your own Dockerfile
Config varsEnvironment variables
Heroku Postgres add-onA PostgreSQL database resource you create on your server
Heroku SchedulerScheduled tasks, defined with standard cron syntax
Release phasePre-deployment and post-deployment commands
Add-on marketplaceOne-click services, or any Docker image you point it at

Two mappings deserve a note.

Release phase to deployment commands. Coolify's pre-deployment command runs in the existing container before the new release goes out, and the post-deployment command runs in the newly built container afterward. Both are configured under the application's Advanced settings, and both execute with sh -c. Migrations that lived in your Procfile's release process type belong in the post-deployment command. Heroku ran release phase against the new slug, but Coolify's pre-deployment command runs against the old image, so a migration shipped in that same commit isn't in that container yet. Keep migrations backward compatible either way, because the new container starts serving before the post-deployment command finishes.

Scheduler to scheduled tasks. Coolify's scheduled tasks run a command inside a container you name, on a cron schedule, and they accept the full five-field syntax plus named schedules like @daily. Worth knowing: they're container-scoped. Heroku Scheduler ran each job in its own one-off dyno, while Coolify execs into the running container, so your app has to be up for the task to fire. If you need something to run on the host rather than inside a container, that's not what this feature does.

The migration sequence

Do this in order. The point is that nothing is irreversible until the last step.

  1. Inventory what you have. Run heroku config -s to dump your config vars and heroku addons to list every add-on. Write down your Procfile process types, any release phase command, your Scheduler jobs, and your custom domains. Lower the TTL on those DNS records now so the cutover is fast later.

  2. Stand up the app on Coolify from the same repo. Connect your Git provider, create a project, add the repo as an application. If you have a Dockerfile, use it. If you were relying on buildpacks, start with Nixpacks and see how far it gets you. Our Coolify deployment walkthrough covers build method selection, port configuration, and the gotchas.

  3. Port the environment variables. Paste them in, minus the ones Heroku generated for you. DATABASE_URL and add-on credentials will be replaced with values from your own services. Watch for variables your framework needs at build time rather than runtime, since those need to be marked as build variables.

  4. Migrate the Postgres data. Create a PostgreSQL database in Coolify first, then move the data. At a high level: pg_dump from your Heroku database connection string in custom format, then pg_restore into the new database with --no-owner --no-acl so it doesn't try to recreate Heroku's roles. Run pg_dump and pg_restore from a Postgres release at least as new as the newer of the two servers, since older client tools refuse to read a newer one. Do a throwaway run now to catch schema problems, and a final run at cutover.

  5. Test on the temporary URL. Coolify gives every application a temporary URL before you attach a domain. Exercise the real paths: logins, background jobs, file uploads, scheduled tasks, anything that touches an external API. This is the step people rush, and it's the one that catches the missing environment variable.

  6. Cut over DNS. Put the Heroku app in maintenance mode and scale any worker dynos to zero (maintenance mode only blocks web traffic; workers and Scheduler jobs keep running), take a final dump, restore it, then point your A record at your Coolify server. That combination is what makes the final dump consistent, so treat it as a planned write freeze and time the throwaway restore in step 4 to size it. With a low TTL, most traffic follows within minutes. Coolify provisions the Let's Encrypt certificate once DNS resolves.

  7. Keep Heroku running. Don't delete anything for at least a week. Leave the app and its database in place, paying for it, until you've watched a full cycle of traffic, backups, and scheduled jobs run clean on the new setup.

  8. Decommission. Remove add-ons, delete the app, cancel what's left. Keep one final Heroku backup somewhere off-platform before the database goes.

What you take on

Here's the honest part. Heroku does real work that becomes yours the moment you self-host.

Platform maintenance. OS patches, Docker upgrades, Coolify updates, disk space, backup verification, uptime monitoring. Heroku did all of this invisibly and charged you for it inside the dyno price. On a VPS you rented yourself, it's your Saturday.

Buildpack behaviour. Heroku's buildpacks encode years of accumulated knowledge about how specific frameworks want to be built. Coolify's automatic detectors, Nixpacks and Railpack, cover a lot of standard projects. When neither matches your setup, you're writing the Dockerfile yourself. That's more control and more work.

Add-on ecosystem. Heroku's marketplace gives you third-party services with their own support and billing. On Coolify you deploy the equivalents onto your own server through one-click services or any Docker image. Broader coverage, but you're the one keeping them running.

The maintenance half of that list is what a managed Coolify host takes back off you. The platform layer gets handled, and you keep the dedicated server and the open-source stack underneath.

Where MapleDeploy fits

MapleDeploy runs managed Coolify on Canadian infrastructure in Toronto. We handle the VM, OS updates, security patches, monitoring, and weekly snapshots. You get the deployment platform without the server administration.

Flat CAD pricing, starting at $45/month for a dedicated 4 GB VM with databases, SSL, and deploys included. Deploy as many apps and databases as the server can hold, with no per-resource billing to forecast. See our pricing for the full plan list.

Move your first app over

30-day free trial on Starter and Pro. Point Coolify at your repo, test on the temporary URL, cut over when you're ready.