Migrate from Heroku to Dokku: what the move involves
Most standard web apps move to Dokku with a git push and a buildpack, but the data, add-ons and day-to-day operations move to your team. If your renewal, a pricing change, a residency requirement or lock-in has started this review, expect the application code to be the easy part. Dokku accepts Heroku-style deploys: it creates an app on the first push to its dokku remote, builds with the herokuish builder (Heroku's v2a buildpack specification), reads the Procfile and supports a release process type that works like the Heroku Release Phase. Apps that rely on Heroku-specific behavior, or on add-ons with no Dokku plugin, need more work. Dokku's own documentation notes that not all Heroku functionality is supported, so check your app.json and every add-on against it before you commit a date.
Platform or DevOps engineers usually do the work, sometimes with an outside implementation partner for the data migration and cutover. The effort is mostly in the datastores and configuration. Each Postgres database must be exported with heroku pg:backups, restored into a Dokku Postgres service with the dokku-postgres plugin, and linked so the app receives DATABASE_URL. Config vars must be re-entered with dokku config:set, and any scheduled jobs, workers or third-party webhooks need to be repointed. Risk is highest at the database cutover, where writes must stop long enough to take a final dump. Plan that window with the application owners, and confirm with your own environment how long the final export and restore take for your database size.
Dokku runs on a single server by default, and that changes who carries operational responsibility. The default docker-local scheduler runs containers directly on the Dokku host, so the host is both your capacity limit and a single point of failure unless you add redundancy yourself. You also take on operating system patching, Dokku and plugin upgrades, TLS renewal behind the proxy, log retention and database backups, which on Heroku were handled by the platform. Before you cut over, set up backups for each Dokku Postgres service, test a restore into a scratch host, and write down a rollback path. Ask any implementation partner for a written scope that names who owns each of these tasks after go-live.
Which Heroku features map cleanly to Dokku and which need rework
This table maps each Heroku feature your app relies on to its Dokku equivalent and labels it clean, needs rework or gap. Read the status column first, then the notes, and check every row your app depends on before you set a cutover date. Most standard web apps map cleanly, while add-ons, scheduled jobs and review-style workflows need a plan.
| Heroku feature | Dokku equivalent | Status and what to check |
|---|---|---|
| Git push deploys | git push to a dokku remote, with apps created on the first push | Clean. Change the remote URL and confirm your deploy key and SSH access on the Dokku host. |
| Buildpacks | herokuish builder using the v2a buildpack specification, or a BUILDPACK_URL variable or .buildpacks file | Clean for standard stacks. Pin any custom buildpack and test it, since Heroku's current stack may differ from herokuish. |
| Build options beyond buildpacks | Cloud Native Buildpacks via the pack CLI, Nixpacks, Railpack or a Dockerfile | Needs rework when you move to a newer builder. Choose one, install the pack CLI if used, and rebuild staging before cutover. |
| Procfile process types | Procfile read under procfile-util strict mode, with ps:scale for each process type | Clean for web and worker types. Fix any Procfile lines that strict mode rejects before the first deploy. |
| Release phase | A release process type that works like the Heroku Release Phase | Clean. Confirm migrations run in the release process and that a failed release stops the deploy. |
| app.json | Dokku app.json for deploy scripts, cron tasks, process formation and healthchecks | Needs rework. Dokku documents that not all Heroku app.json functionality is supported, so compare each key you use. |
| Config vars | Environment variables set with config:set on the app | Clean for values. Export them with the Heroku CLI, review secrets and avoid copying them into a repository. |
| Heroku Postgres and DATABASE_URL | dokku-postgres plugin with postgres:import and postgres:link, which sets DATABASE_URL | Needs rework. Restore with pg_restore and check extensions, roles and PostgreSQL version against your source database. |
| Custom domains and HTTPS | Domains set on the app, with nginx by default or Caddy, HAProxy, OpenResty or Traefik as alternatives | Needs rework. Point DNS in stages and confirm certificate issuance on the proxy you choose. |
| Scaling with ps:scale | ps:scale for process counts on the single Dokku host | Clean for vertical and per-process counts. Horizontal scaling across servers needs the k3s scheduler and a registry, so treat it as a separate project. |
| Heroku Redis, Scheduler and Review Apps | Redis plugin, cron tasks in app.json and no direct review app equivalent | Gap to confirm. Check each with its own sibling page before deciding how you will replace it. |
What data and integrations you must move
A Heroku app is rarely just code. The database, config vars, files, webhooks and DNS records each move through a different path, and each can break production if it is missed. Work through these cards as an inventory, and confirm each item in your own environment before you schedule the cutover.
Heroku Postgres export with pg:backups
Take a fresh backup with heroku pg:backups:capture, then download it with heroku pg:backups:download. Heroku PGBackups produces a pg_dump custom format file. Heroku's signed download URL expires after 60 minutes, so download the file in one sitting and verify its size before you start the restore. Heroku advises PGBackups only for moderately loaded databases up to 20 GB. Above that size, plan a logical export instead and confirm the approach with your database owner.
Restore and import into dokku-postgres
Create the target service with dokku postgres:create, then import the dump with dokku postgres:import, reading from stdin or a file on the Dokku host. If you restore the dump with pg_restore into another PostgreSQL database first, use the --no-acl and --no-owner options, because Heroku role names will not exist on your server. Run the restore against a staging copy and compare row counts for the largest tables before you continue.
Link services and DATABASE_URL
Running dokku postgres:link sets the app's DATABASE_URL, the same variable Heroku uses to point an app at its primary Heroku Postgres database, so most application code needs no change. Check for any code that reads a hard-coded host, a Heroku-specific HEROKU_* variable or a second database URL. Confirm that every add-on the app uses, including Redis and search, has an equivalent Dokku plugin or an external service with a plan.
Config vars and secrets
Export the current values with heroku config, then set them on the Dokku app with dokku config:set. Treat this export as sensitive, because it holds every credential the app uses. Rotate third-party API keys, session secrets and database passwords after cutover, since the old values have passed through a laptop and a chat or ticket. Confirm which values differ between staging and production so that a copied value does not point a live app at test services.
File storage and uploaded assets
Heroku's filesystem is ephemeral, so uploads written to the dyno disk are already lost on every restart and should be in an external store. Identify where user uploads, generated reports and exports live, usually an S3 bucket or a similar service. Dokku's local storage is persistent on the host, so decide whether files stay in that bucket, move to a self-hosted S3-compatible store or sit on a mounted volume, and back that location up separately from the database.
Third-party webhooks and scheduled jobs
Payment providers, identity providers, email services and Git hooks often call a fixed Heroku hostname. Update each callback URL and secret in the provider console, and plan a short window where both old and new endpoints accept events. Check the Heroku Scheduler jobs and any app.json cron entries, because a job that runs on both platforms at once can send duplicate emails or charges. Confirm each provider's retry behavior so that events sent during cutover are not lost.
DNS records and TLS certificates
List every hostname that points at the Heroku app, including apex, www, custom subdomains and any records used for domain verification. Lower the TTL well before the change, then point each record at the Dokku host. Dokku's default proxy is nginx, and it can use Caddy, HAProxy, OpenResty or Traefik instead. Confirm that the certificate issuance method works for every hostname before you remove the Heroku record, and keep the Heroku app running until traffic has moved.
Migration sequence from inventory to decommission
This sequence moves one Heroku app and its Postgres data to Dokku in stages, so each step produces a checkable result and the Heroku app stays live until the new host has proven itself.
Inventory apps, add-ons, config vars and domains
The app owner lists every Heroku app in scope, its process types from the Procfile, its buildpacks, its add-ons, its config vars and every hostname that points at it. The inventory also records which add-on backs DATABASE_URL and whether app.json defines deploy scripts, cron tasks or healthchecks. Confirm each item against the Heroku CLI and the Dashboard rather than memory. The output is a signed-off checklist with one row per app and per dependency, including any add-on that has no Dokku plugin equivalent, which then needs a replacement decision before go-live.
Deploy a pilot app with git push
Choose a low-risk app that is representative of the estate and create it on the Dokku host. Add a dokku git remote and push the code, then confirm that the builder selected by Dokku matches what Heroku used. For a custom buildpack, set BUILDPACK_URL or a .buildpacks file in the repository root. Scale the web process, check that the release process type runs, and load config vars with the config commands. The output is a running pilot on a Dokku hostname with a passing health check and a recorded list of any Heroku-specific behavior that did not carry over.
Rehearse the database restore
Create a dump with heroku pg:backups:capture and download it with heroku pg:backups:download before the signed URL expires, which Heroku documents as 60 minutes. Heroku recommends PGBackups for databases up to 20 GB, so larger databases need a logical export instead. Create a PostgreSQL service with the dokku-postgres plugin, import the dump with postgres:import, and restore with pg_restore using --no-acl and --no-owner. Link the service to the pilot with postgres:link so DATABASE_URL is set. Run row counts and a few application queries against both databases. The output is a restored database whose counts match the source, with the rehearsal timings recorded for the cutover plan.
Run a parallel staging environment
Point a staging hostname at the Dokku pilot and run the full application against the restored database, including background processes, scheduled jobs and any outbound webhooks. Keep the Heroku app as the only production target. Ask the application owner to test sign-in, file uploads, email delivery and payment callbacks, because these often depend on hostnames or secrets that differ between platforms. Check certificate issuance for the staging hostname through the proxy Dokku uses, which is nginx by default. The output is a written test result for each user path, with defects logged and closed before cutover.
Schedule and rehearse the DNS cutover
Lower the TTL on the production record well ahead of the change window so that resolvers pick up the new value quickly, and confirm the current TTL with the DNS provider. Write a cutover runbook that lists the final data sync from Heroku Postgres, the write freeze on the Heroku app, the record change, the health checks and the owner for each action. Include a rollback path that points the record back to Heroku and states who makes that call and on what signal. Walk through the runbook with the on-call team. The output is a signed-off cutover runbook with named approvers and a tested rollback procedure.
Execute cutover and monitor
Freeze writes on the Heroku app, take a final pg:backups capture, restore it into the Dokku database, and then change the DNS record. Watch error rates, response times, logs and database connections against the baseline recorded in the inventory. Keep the Heroku app running and its database intact so the rollback path remains available. Verify the public hostname, the TLS certificate and the scheduled jobs. The output is a cutover log with timestamps, the post-change health check results and an explicit go or rollback decision made by the named approver.
Decommission the Heroku app after a stable period
Keep the Heroku app and its database available until the owner agrees that the new environment has run stably through its normal cycle of traffic and jobs. Then export a final backup for retention, remove the domains from Heroku, scale dynos to zero, and delete the add-ons and the app. Confirm that backup copies are stored where your retention policy requires. Record the decommission date and the retained backup location in the migration record. The output is a closed migration record showing that no production traffic, data or scheduled work still depends on Heroku.
How to run Heroku and Dokku in parallel and cut over safely
Keep Heroku live and serving production while Dokku runs the same app on a staging hostname, such as a subdomain you control that does not yet appear in public DNS. Deploy to the Dokku remote with git push dokku main, attach the same domain list you will use in production, and test the paths that matter: sign-in, webhooks, file uploads, background jobs and any outbound calls that depend on source IP allowlists. Because Dokku's proxy and TLS setup are separate from Heroku's, the staging hostname is also the place to confirm that certificates issue and renew before any traffic moves. Dokku ships nginx as its default proxy, with Caddy, HAProxy, OpenResty and Traefik documented as alternatives in the Dokku proxy management guide. Decide which one you run before cutover, then check how it terminates HTTPS for each domain, since a certificate that works on the staging name can still fail on the production name if the domain was never added to the app.
The hard part is data. Heroku keeps accepting writes until you stop it, so you need a decision on which system is the source of truth during the window. For most apps the safest pattern is a short write freeze: put Heroku into maintenance mode or stop its web and worker processes, take a final pg:backups capture or a direct pg_dump of the database, load it into the Dokku PostgreSQL service with dokku postgres:import, link it so DATABASE_URL is set, and then verify row counts and key tables before reopening writes. Ask the team which tables change continuously, such as sessions, queues and event logs, and whether replaying a delta is worth the risk. Plan the freeze length from your own measured restore time on a copy of the data, not from any general estimate, and rehearse the whole sequence at least once against a recent backup.
DNS is the last step and the one you do not fully control. Lower the record TTL on the production hostname well ahead of the cutover so resolvers pick up the new target quickly, then point the record at the Dokku server once the freeze is complete and verification has passed. Recursive resolvers, client caches and corporate DNS may keep returning the old address for longer than the TTL you set, so keep Heroku running with a rollback path until traffic has clearly stopped arriving there. Confirm the rollback steps in writing: which DNS record reverts, who can make the change and what happens to writes accepted on Dokku during the interval. Also confirm with your registrar and certificate process that the change does not break validation for the domain.
What drives the cost and duration of the move
This table lists the factors that change how much work and recurring effort a Heroku to Dokku move takes, why each one matters and how to keep it under control. Read it before requesting a quote, because the same app count can produce very different effort depending on database size, add-ons and staff availability. The main takeaway is that the one-time migration work is only part of the cost, since the server, patching and backup time continue after cutover.
| Driver | Why it changes cost or effort | How to keep it down |
|---|---|---|
| Number of apps and process types | Each app needs its own Dokku app, domains, TLS, scaling settings and deploy test, and each extra process type (web, worker, clock) adds configuration to verify. | Group apps with identical buildpacks and process layouts, and migrate low-risk apps first to build a repeatable runbook. |
| Database size against the 20 GB PGBackups guidance | Heroku advises PGBackups only for moderately loaded databases up to 20 GB, so larger databases need logical backups and a longer import window (Heroku Dev Center). | Check database size and load before scheduling, and plan a logical dump with a rehearsed restore for anything above that guidance. |
| Add-ons beyond Postgres | Redis, search, queues or email add-ons each need a matching Dokku plugin or a self-hosted service, plus new credentials and client configuration. | Inventory every add-on and config var first, and map each to an official Dokku plugin or a service you already run. |
| Custom buildpacks | Dokku uses herokuish for Heroku v2a buildpacks, while a custom buildpack needs a BUILDPACK_URL or .buildpacks file and may need a rebuild or a move to Cloud Native Buildpacks, Nixpacks or a Dockerfile. | Pin buildpack versions, confirm they build with herokuish, and test a single build before planning the rest. |
| app.json hooks and unsupported Heroku features | Dokku supports part of the app.json format, and deploy scripts, cron tasks and healthchecks must be checked one by one because not all Heroku functionality is supported. | Compare app.json against Dokku's documented support, and rewrite unsupported hooks as explicit Dokku commands or scheduled jobs. |
| Compliance and audit evidence | Regulated workloads need documented access control, backup testing, logging and change records on the Dokku host, which adds design and evidence work that Heroku's managed tier partly covers. | Agree the control list and evidence format in writing before work starts, so the scope includes what an assessor will request. |
| Staff availability and on-call coverage | Someone must own patching, certificate renewal, backup monitoring and restores after cutover, and a small team may not have that time without planning. | Name an owner and a backup owner before cutover, and ask whether the scope covers handover or an ongoing support retainer. |
| Server or servers you rent | You pay the provider for the Dokku host, storage and bandwidth every month, and a single server means one host to size for the combined app load. | Size memory and disk from measured Heroku usage, and ask the provider for the monthly line items and snapshot pricing before deciding. |
| Optional paid Dokku Pro | Dokku Pro is an optional paid product, so it adds a recurring line item only if you choose it, and its terms apply on top of the free MIT-licensed core. | Confirm which features you need with the Dokku project, and compare that against the free core before adding a recurring fee. |
What usually goes wrong and how to prevent it
Most failed Heroku to Dokku moves fail on data, configuration or capacity, not on the build. The cards below cover the risks that recur in this migration and the check that prevents each one. Work through them before the DNS cutover, not after.
Restore that was never rehearsed
A dump that restores cleanly on paper can still fail on a real Dokku host because of extensions, roles or ownership. Restore into a scratch dokku-postgres service first, using the --no-acl and --no-owner options that Heroku documents for pg_restore, and then run the application's read paths against it. Time the restore so you know how long the production window needs to be.
Stale dump taken before cutover
A pg:backups capture taken days before the switch will miss every write made since. Plan a final capture after you stop writes to the Heroku app, then import that dump into Dokku and compare row counts on the critical tables. Name the owner of the freeze window in the runbook so nobody keeps writing to Heroku after the dump starts.
Expired signed download URLs
The signed URL that heroku pg:backups:download returns expires after 60 minutes. Teams that copy the link into a ticket and fetch it the next morning end up with a 403 and a second capture. Download the file in the same session, store it on the target host with a checksum, and keep a copy in storage you control before you start the import.
Config vars that never made it across
Heroku config vars do not move on their own. Export them with heroku config, set them with dokku config:set, and diff the two lists before cutover. Check DATABASE_URL after dokku postgres:link, and check any third-party keys, webhook secrets and feature flags that the app reads only at boot, because a missing value often shows up only on a code path nobody tests.
Buildpack behavior that differs from herokuish
Dokku's default herokuish builder follows Heroku's v2a buildpack specification, but the image contents, stack and cache behavior are not identical to Heroku's. Pin the buildpack or set BUILDPACK_URL explicitly, then compare the built app's runtime version, system packages and start command with production. If the build relies on Heroku's Fir or Cloud Native Buildpacks stack, test the Cloud Native Buildpacks builder instead of assuming parity.
app.json features Dokku does not support
Dokku reads an app.json based on Heroku's format for deploy scripts, cron tasks, process formation and healthchecks, but documents that not all Heroku functionality is supported. Inventory every key in your app.json against the Dokku docs before the first deploy. Anything unsupported, such as review app or addon directives, needs a replacement you write and test, such as a Dokku plugin or a scheduled job.
One host with no backup plan
Dokku's default docker-local scheduler runs containers directly on one Dokku host, so a disk failure takes the apps and their data down together. Set up scheduled backups for the Postgres service, for example to S3-compatible storage, and test a restore onto a second server. The k3s scheduler can spread work across several servers, but it requires a Docker registry and only the initial Dokku server accepts git push, so size the first host for peak load and plan the second host before you need it.
How the move changes by app count and starting point
A single web app with a modest Postgres database is the simplest case. Dokku runs on one server, so the work is mostly a one-time rebuild: install Dokku, create the app, push the code to the dokku remote, and import the data. The application code usually needs no structural change if its Procfile and buildpack already work on Heroku. The main decisions are whether the app can tolerate a short write freeze during the final data copy and whether you want the server to hold backups off-host.
- Build path: Heroku's buildpacks are usually reproduced by Dokku's herokuish builder, so a standard Ruby, Node or Python app often deploys with little change.
- Database move: A single
pg:backupsdump restored into thedokku-postgresplugin is a manageable task at this size. - Config vars: Only a handful of variables need to be re-entered, which makes a manual copy practical.
- Operations: You take on patching, TLS renewal and backups for one host, which is the main ongoing change.
- Rollback: Keeping the Heroku app running until DNS has moved gives you a clean fallback.
Confirm first: the buildpack the app uses today, the Postgres version, and whether any add-on other than Postgres is attached.
A portfolio of apps sharing one Dokku host changes the work from migrating apps to designing a host. Each app gets its own Git remote, domain and config, but they all compete for the same CPU, memory, disk and reverse proxy. Dokku's single-server model makes this practical, but capacity planning becomes the first task rather than an afterthought. Database plugins can serve several apps, so you also need a clear rule for which database belongs to which app.
- Capacity: Size the server for the combined peak of all apps, and test the busiest app under load before cutover.
- Isolation: Give each app its own database service and its own credentials so one app's migration does not expose another's data.
- Routing: Plan domain names, the proxy choice and certificate handling for every app before the first DNS change.
- Sequencing: Move low-risk apps first and keep the busiest app for last, after the team has a repeatable runbook.
- Scaling limits: Dokku's default scheduler runs on one host, so growth past that point is a separate decision.
Confirm first: the expected combined load, which apps share data, and whether any app needs a separate server for compliance or isolation reasons.
A larger Postgres database changes the backup method. Heroku's documentation advises PGBackups for moderately loaded databases up to 20 GB and points larger databases to logical backups. The signed download URL for a PGBackups file also expires after 60 minutes, so a slow transfer can fail. For a bigger database, the cutover plan usually depends on how long the data copy and restore take, and that time is set by your network and disk speed rather than by Dokku itself.
- Backup method: Consider a logical dump with
pg_dumprun directly against the database, which Heroku's documentation treats as the path for larger data sets. - Transfer window: Download the file promptly after creation, because the 60-minute URL limit applies to the Heroku-side link.
- Restore target: Use
pg_restorewith--no-acland--no-ownerwhen loading into thedokku-postgresplugin. - Write freeze: Measure how long the final copy takes in a rehearsal and plan the maintenance window around that figure.
- Replication option: Logical replication can reduce downtime, but it needs a separate setup and testing.
Confirm first: the database size on Heroku, the restore duration in a test run, and whether the target server has enough free disk for the dump and the restored data.
Teams with custom buildpacks, or apps that rely on Heroku-specific behavior, need a compatibility review before any data moves. Dokku reads Procfiles under procfile-util strict mode and supports a release process type, and it reads app.json for deploy scripts, cron tasks, formation and healthchecks. Its documentation states that not all Heroku functionality is supported, so each feature must be checked rather than assumed. Dokku can also build with Cloud Native Buildpacks, Nixpacks, Railpack or a Dockerfile, which can be a useful path when the herokuish build fails.
- Custom buildpacks: Set them with
BUILDPACK_URLor a.buildpacksfile in the repository root, and test the build output against the Heroku slug. - Procfile behavior: Check that every process type maps to a Dokku process and that the release process runs as expected.
- Add-ons and features: Inventory every add-on, Heroku Scheduler job, Review App and private networking rule, since each needs an owner on Dokku.
- Alternative builders: A Dockerfile gives the most predictable build when buildpack behavior is hard to reproduce.
- Environment parity: Confirm stack and runtime versions match what the app runs on Heroku today.
Confirm first: the full list of buildpacks, the app.json features in use, and any Heroku-only dependency that the app calls at runtime.
Questions teams ask before moving from Heroku to Dokku
How much effort and cost should we expect to move from Heroku to Dokku?
The cost is mostly labor and a server, not a license, because Dokku is MIT licensed and has no per-dyno fee. The effort depends on how many apps you run, how many use add-ons, and how much of your build relies on Heroku-specific behavior. Recurring costs are the rented server, backups storage, and the time someone spends on patching, upgrades and on-call. One-time costs are the migration itself, the database cutover and testing. Ask any quote to list each app with its buildpack or Dockerfile, each add-on to be replaced, the backup and restore plan, the staging environment, and who patches the host after handover.
How long does a Heroku to Dokku migration take?
The duration depends on your inventory more than on Dokku itself. Count the apps, their process types, the number of databases and their size, the config vars, and any app.json features that Dokku documents as partially supported. A small app with a standard buildpack and no add-ons is a short exercise. An app with a large Postgres database, scheduled jobs and a custom buildpack needs more testing and a planned restore window. Dokku does not publish a migration duration, so ask for a timeline in the written scope that is tied to a named inventory and a dated cutover window.
Can our team migrate from Heroku to Dokku without outside help?
Yes, a team with Linux, Docker and PostgreSQL experience can do it, and Dokku's documentation covers git push deploys, buildpacks, Procfiles and the dokku-postgres plugin. The harder parts are operational: hardening the host, configuring the proxy and TLS, setting up backups you have tested, and handling Postgres upgrades yourself. Heroku's managed platform hides those tasks, so they move onto your team's calendar. If your team has little time for on-call work, consider a fixed-scope engagement with DeployOpen for planning, implementation and handover, and agree the support model in writing.
What access, test environment and restore window does a Dokku migration need?
You will need administrative access to the target server or cloud account, DNS control for the cutover, and access to the Heroku apps, config vars and Postgres backups. A staging Dokku host is strongly advisable so you can deploy each app with its real config and run its tests before any traffic moves. Plan a restore window for the database: the dump is taken, transferred and loaded, and the app is paused or set to read-only while the final data is copied. Confirm the window length with your own data size and change-freeze rules rather than assuming one.
Does Dokku handle backups and patching for us?
No. Dokku is a platform you run on your own server, so backups, patching and recovery are your responsibility or your provider's. The dokku-postgres plugin can run one-off or scheduled backups to S3-compatible storage, and it can export and import databases, but you must configure storage, test restores and monitor the jobs. Operating system and Dokku updates are also your work. Coolify's guide for teams coming from managed platforms makes the same point: a rollback can redeploy an application image but does not restore databases or persistent files. Ask the quote to include a backup schedule, a tested restore procedure and a patching cadence.
Do we have to keep Heroku data in a specific region after moving to Dokku?
Dokku runs on whatever server you choose, so the region is set by your hosting account and not by Dokku. If your data residency rules require an EU or national region, pick a server and database location there and confirm that backups and off-site copies stay in the same jurisdiction. Heroku's pg:backups snapshots are stored in the United States regardless of database region, so that step needs a separate plan during the exit. Confirm with your compliance owner which regions, storage locations and transfer paths are allowed before you export any data.
Does Dokku support every Heroku feature our apps depend on?
Not every feature. Dokku supports Heroku-style git push deploys, herokuish buildpacks, Procfiles, a release process type and an app.json format based on Heroku's, but Dokku documents that not all Heroku functionality is supported. Dokku also provides no datastores by default, so each add-on needs a plugin or a self-managed service. Review each app's Heroku features, including add-ons, scheduler jobs, pipelines and Private Space networking, against Dokku's documentation and list the gaps in your scope document with a decision for each one.
Which parts of the Heroku setup need the most checking before cutover?
Focus on four areas: the build, the database, configuration and the traffic switch. Confirm each app builds with the same buildpack or with a Dockerfile, and that the Procfile process types and the release step behave as before. Restore the Postgres dump with the no-acl and no-owner options, then link the database so DATABASE_URL is set. Re-create config vars as Dokku config values and check secrets are not copied into logs. Finally, lower the DNS TTL in advance and keep the Heroku app running until the new host passes checks.
How DeployOpen scopes and runs a Heroku to Dokku migration
DeployOpen plans, implements, hardens, backs up and documents self-hosted open-source platforms inside your own cloud accounts, data centers or air-gapped networks. For a Heroku to Dokku move, we start with a written scope that maps each app, datastore and cutover step to a deliverable before any work begins.
How we scope
- Inventory every Heroku app, process type, add-on and custom buildpack before agreeing the scope in writing.
- Confirm which Heroku Postgres databases move, their sizes and whether pg:backups suits each one.
- List the domains, certificates, config vars and external services that must change during DNS cutover.
How we build
- Install Dokku on servers you control, configure the reverse proxy and wire each app to its builder.
- Test Procfile, release process and app.json behavior against each app, recording any Heroku features that do not carry over.
- Restore databases into the dokku-postgres plugin, link each app to its DATABASE_URL, and verify data and access rules.
How we hand over
- Deliver runbooks for deploys, rollbacks, database restores, scaling with ps:scale and config var changes.
- Document backup schedules, retention, restore tests and the controls your compliance team needs to review.
- Walk your engineers through the cutover checklist and the rollback steps agreed before the DNS change.
How we support
- Offer ongoing support, upgrades and continuous testing as a retainer after the fixed-scope engagement ends.
- Plan Dokku and PostgreSQL upgrades in writing, including the major version support dates you must track.
- Confirm with your team which operational tasks stay in-house and which the retainer covers before handover.

