Fly.io or Dokku: when one server is enough
For a single-region web app with a Postgres database and steady traffic, Dokku on one server can replace Fly.io hosting. Dokku's default docker-local scheduler runs containers directly on the Dokku host, so the app, its processes and its datastores share one machine. A K3s cluster through the k3s scheduler can spread the workload across several servers, but it requires a Docker registry, and only the initial Dokku server is the git push target. Fly.io's regional Machines remain the better fit when the app must run close to users in several regions, when the renewal or cost review depends on per-region placement, or when no one on the team wants to patch and monitor servers. Four decision points settle the choice: multi-region placement, stateful services, team operating capacity and build method. Check each one against the app before you pick a target.
Build method is usually the quickest check. If the app ships a Dockerfile, Dokku's Dockerfile builder is often the simplest path to reproduce the current build. If the app relies on Cloud Native Buildpacks, Dokku's CNB builder defaults to a stack based on heroku/builder:24 and needs the pack CLI installed separately, so test the build on a staging server first. Stateful services need more planning. Dokku provides no datastores by default, and the official dokku-postgres plugin adds PostgreSQL, sets DATABASE_URL when linked and runs backups to S3-compatible storage. Ask whether your current Fly.io Postgres data, volumes and backup schedule can be reproduced on that plugin, and who restores the data when the server fails.
Process types and release commands map well. Dokku reads a Procfile, scales the web process to 1 on the first deploy, supports a release process type that works like Heroku's Release Phase and scales other process types with ps:scale. The gap is placement. Dokku cannot run a process in a second region, and a failure of the host stops every app on it. Before you commit, write down the recovery time the business requires, confirm who applies operating system and Docker updates, and ask the cost review to price the server, backups and on-call time next to the current Fly.io bill. Compare the totals, not the hourly rate, because the server cost is fixed while Fly.io usage moves with traffic.
Which platform fits which workload
This table maps common Fly.io workloads to the better fit between Fly.io and a single-server Dokku install, and names the condition that decides each choice. Read each row against your own traffic pattern, data rules and team capacity rather than assuming Dokku reproduces Fly.io's regional placement.
| Workload | Better fit | Deciding condition |
|---|---|---|
| Single-region web app with one Postgres database | Dokku on one server, if the database and app can share one host | Traffic fits one machine, you can run backups with the dokku-postgres plugin, and a restore is tested |
| App that must serve users in several regions | Fly.io | Dokku runs on the servers you give it and has no equivalent to placing Machines by region, so latency gains need your own multi-site design |
| Team with no platform engineers | Fly.io, unless a managed contract is in place | Someone must own OS patching, proxy and TLS configuration, backups and upgrades on Dokku, and that person must exist |
| Workload with background processes and cron jobs | Dokku, with a Procfile worker type and app.json cron tasks | Heroku-style process types and cron map cleanly, but confirm each app.json field you rely on is supported |
| Release commands that run before traffic shifts | Dokku, using the release process type | Your migration steps must be idempotent and tested, since rollbacks do not restore databases or persistent files |
| Regulated workload that must stay on named servers | Dokku on servers you control | Your assessor needs to see host location, access paths and backup storage, which Fly.io's regional model may not satisfy for your scope |
| Stateful app with large volumes or heavy database load | Confirm first, then decide | Dokku's default docker-local scheduler runs on the host, so disk, memory and failover needs drive the choice more than the deploy workflow |
| Multi-server app needing scheduling across hosts | Dokku with the k3s scheduler, or Fly.io | K3s needs a Docker registry and only the initial server is the git push target, so validate the setup before committing |
How builds, process types and databases compare
Dokku deploys with a git push to a dokku remote, and it creates the app on the first push if it does not exist. The build step is chosen by the builder in use. The default path uses herokuish, which implements Heroku's v2a buildpack specification, so apps written for Heroku-style buildpacks generally build without changes. Dokku also builds from a Dockerfile, which is the closest match to a Fly.io app built from its own Dockerfile.
- Dockerfile builds: Dokku builds the image from your Dockerfile on the server, which keeps the build input the same as Fly.io's Dockerfile path.
- Cloud Native Buildpacks: The CNB builder uses the
packCLI, which you install separately on the host. Its default stack is based onheroku/builder:24. - Nixpacks and Railpack: Dokku documents Nixpacks from 0.32.0 and Railpack from 0.37.0, selected by a
nixpacks.tomlorrailpack.jsonfile in the repository. - Build location: Fly.io builds on its own remote builders. On Dokku, the build uses CPU and disk on the server that also runs your app, so large builds can compete with live traffic.
Confirm which builder your team needs, then test it against your real dependency tree on a staging host before cutover.
Dokku reads a Procfile under procfile-util strict mode rules, so each process type needs a clear command. The first deploy scales the web process to 1. Other process types, such as workers, are scaled with dokku ps:scale. Dokku also supports a release process type, which runs before the new version receives traffic in the way Heroku's Release Phase does. This is the closest equivalent to the release commands you may have defined in fly.toml.
- Procfile handling: Process types are declared in the
Procfile, and strict mode rejects lines that do not follow the expected format. - Release process: A
releaseentry runs a command such as schema migrations during deploy. Check how it reports failure, because a failed release must stop the rollout. - Scaling:
ps:scalesets the count for each process type on one host. There is no per-region placement, so every process runs where the Dokku host runs. - Health checks and app.json: Dokku reads an
app.jsonmanifest based on Heroku's format for deploy scripts, cron tasks and healthchecks. Dokku documents that not all Heroku features are supported, so test each item you use.
Review your fly.toml process groups, release command and check definitions against these Dokku equivalents before you migrate.
Dokku provides no datastores by default. Official plugins add services such as Postgres, MySQL, Redis and others. Each service is created with a plugin command and then linked to an app, and linking a Postgres service sets the app's DATABASE_URL environment variable. This replaces the attachment step you would perform with Fly.io Postgres, but the data lives on the Dokku host and its disks, not in a managed cluster.
- Plugin model: The
dokku-postgresplugin installs PostgreSQL 18.6 by default and manages the container lifecycle. - Backups: The plugin exports and imports databases with
postgres:exportandpostgres:import, and it runs one-off or scheduled backups to S3-compatible storage. - Failover: Dokku does not provide automatic failover or replication for these databases. You must design a standby, promotion process and recovery runbook yourself.
- Volumes: Persistent storage is host disk managed by Dokku. Plan disk capacity, monitoring and any snapshot policy at the server level.
- Restore testing: A scheduled backup is only useful after a restore test on a separate host.
Before migrating data, confirm the PostgreSQL version, backup retention and restore time you need, and document who runs failover.
Capabilities side by side, with the gaps stated
This table compares how Fly.io and Dokku handle builds, process types, release steps, Postgres, scheduling, placement, TLS and licensing. Read the Dokku column as documented behavior, and confirm Fly.io specifics such as machine sizing, volume behavior and private networking in Fly.io's current documentation. Gaps for Dokku are marked plainly so you can judge fit before planning a migration.
| Capability | Fly.io | Dokku |
|---|---|---|
| Build methods | Builds from a Dockerfile or buildpacks, with the build defined in the app's Fly.io configuration. Confirm current builder options in Fly.io's docs. | Builds from a Dockerfile, Cloud Native Buildpacks through the pack CLI, herokuish, Nixpacks or Railpack, or a Lambda or null builder. Gap: the pack CLI must be installed separately. |
| Deploy trigger | Deploys from the Fly.io CLI or CI pipelines pushing to its API, not a Git remote on your server. | Deploys with git push to a dokku remote, creating apps on the fly. Gap: only the initial Dokku server is the git push target in multi-server setups. |
| Process types | Runs process groups as separate Machines, configured per app. Confirm how process groups map to Machines in current docs. | Reads Procfile process types and scales them with ps:scale. The web process scales to 1 on the first deploy. |
| Release commands | Supports release commands that run before new Machines start. Confirm timing and failure behavior in Fly.io's docs. | Supports a release process type that works like the Heroku Release Phase, defined in the Procfile. |
| Postgres | Managed Postgres is a separate paid service, and you can also run Postgres yourself on Fly.io. Confirm backup and failover options for the version you choose. | No datastore by default. The official dokku-postgres plugin installs PostgreSQL 18.6 by default, supports export and import, and runs backups to S3-compatible storage. Gap: you operate failover yourself. |
| Scheduling model | Runs apps as Machines started and stopped through Fly.io's control plane, with usage-based billing. | Default docker-local scheduler runs containers on the Dokku host. The k3s scheduler, from Dokku 0.33.0, needs a Docker registry. Gap: no managed autoscaling of the kind a usage-billed platform provides. |
| Placement and regions | Places Machines across regions worldwide and lets you choose where apps run. Confirm the regions available to your organization. | Runs on a single server by default. Multi-server placement needs the K3s scheduler. Gap: no built-in multi-region placement across providers. |
| TLS and reverse proxy | Terminates TLS at Fly.io's edge with managed certificates. Confirm certificate issuance details in Fly.io's docs. | Ships nginx by default with Caddy, HAProxy, OpenResty and Traefik as alternatives. You manage certificates and renewal on your server. |
| Licensing | Proprietary managed service billed by usage. | MIT licensed, with the license file in the dokku/dokku repository carrying a 2014 copyright by Jeff Lindsay. |
| Supported host operating systems | Not applicable. You do not manage the host OS. | Ubuntu 22.04, 24.04 and 26.04, and Debian 11 or later, on AMD64 or arm64, with 1 GB RAM for the default Docker scheduler. |
What regional Machines do that a Dokku host does not
Dokku's default scheduler runs every container on the Dokku host itself. With the docker-local scheduler, an app's processes start as containers on the one server that received the git push. There is no placement decision to make and no second region to choose. Fly.io, by contrast, lets you start Machines in any of its regions and route users to the nearest one, so a single app can have compute close to users in several countries. If your audience is spread across regions and latency matters to them, Dokku on one server cannot match that placement, and the gap is structural rather than a setting you can tune.
The k3s scheduler, introduced in Dokku 0.33.0, is the closest Dokku gets to multi-server placement. It builds a K3s cluster across several servers, but the cluster is still a single operational unit that you manage. It requires a Docker registry, so every build must be pushed to a registry your cluster can pull from, and that registry becomes a component you run, back up and secure. Only the initial Dokku server is configured as the git push target, so the other nodes are compute capacity rather than deployment entry points. Confirm in the Dokku K3s documentation how node placement, storage and networking are handled before you assume it covers your multi-site design.
Accept this gap only when the business case does not need users served from nearby regions. A single-region audience, or an EU-only user base served from one country, often fits a single Dokku host or a small K3s cluster in one location. Before you decide, check three things with your team: where your users actually are, what latency and data residency requirements your contracts state, and whether a second site would need its own registry, database replication and failover plan. If the answer to any of these requires regional routing, plan for a different platform rather than stretching Dokku beyond its model.
Where data, traffic and access sit under each model
Fly.io runs your Machines, networking and certificates on infrastructure it operates in regions it lists, while Dokku puts those duties on the team that runs the server. The cards below separate what moves to your own cloud account or data center from what Fly.io handles for you, so you can check each one before choosing.
Data location and regional placement
On Fly.io, app and volume data sits in the regions you select from Fly.io's list. With Dokku, the app containers, Postgres data and backups sit on whichever server or cluster you provision, so residency is set by your own choice of provider and site. Confirm regional commitments, subprocessors and data processing terms directly with Fly.io, and confirm which locations your own compliance scope accepts.
Host patching and operating system updates
Fly.io operates the underlying hosts that run your Machines, so host patching is part of the service you buy. With Dokku, the team must patch the operating system, the Docker engine and the Dokku release on each server, and Dokku's own installation notes list the supported Ubuntu and Debian versions to plan around. Assign a named owner for these updates before go-live.
TLS and reverse proxy handling
Fly.io terminates TLS and issues certificates as part of its platform. Dokku ships nginx by default and can use Caddy, HAProxy, OpenResty or Traefik instead, so the team chooses the proxy, issues or renews certificates, and tests the domain configuration after each change. Check the proxy setting and certificate renewal path on the server you run, because a missed renewal fails at the edge, not in the app.
Backups and restore ownership
On Fly.io, volume snapshots and database backups are features you configure within Fly.io's service. With Dokku, backups for each Postgres service depend on the plugin: the official dokku-postgres plugin can run scheduled backups to S3-compatible storage, but the team owns the bucket, retention, encryption keys and restore tests. A rollback to a previous image does not restore databases or persistent files, so plan restore drills separately.
Identity and access to the git push target
Fly.io deploys through its CLI and API under accounts and tokens that Fly.io administers. Dokku deploys with git push to a dokku remote on the server, so SSH keys, who can push, and who holds root on the host decide who can change production. Review the list of SSH keys, remove departed staff, and restrict host access, because the server itself becomes the deployment control point.
Compliance evidence and audit scope
Fly.io supplies some controls and documentation for its platform, and those reports cover only its own environment. With Dokku, your team generates the evidence: host configuration, access reviews, patch records, backup test results and change logs. DeployOpen can prepare these controls and evidence for an assessment, but independent auditors decide whether they meet your requirements. Confirm your compliance scope with your auditor before deciding which evidence to collect.
What the Fly.io bill and the Dokku server budget each include
The Fly.io bill is usage-based, so it is built from several metered components rather than one fixed fee. Expect charges for Machines (the compute that runs each app process), volumes, snapshots, outbound egress, IPv4 addresses, certificates and any support plan you buy. Each line moves with traffic, memory size, storage held and the number of Machines running. Pull the current rate for each component from Fly.io's pricing page rather than relying on this page, because rates change. Ask your finance team to export the last few invoices and map each line item to one app, one region and one environment, so you can see which components actually drive the total before you size a Dokku server.
The Dokku budget is mostly fixed infrastructure plus people. Dokku itself is MIT licensed, so there is no license fee, but you pay for the server or servers that run it, for any S3-compatible storage that holds database backups from the dokku-postgres plugin, and, if you use the k3s scheduler, for a Docker registry that the cluster needs. Your largest recurring cost is usually staff time for patching the host, monitoring, upgrades and incident response, and that time is not free. Dokku's docs state a 1 GB RAM requirement for the default Docker scheduler, but server size, count and region cost are priced by your hosting provider, so confirm them in their quote.
Some costs are one-time and some recur. Setup, the first migration of apps, data and DNS, and the initial hardening are one-time, although they can take longer than the platform team expects. Servers, storage, backups, registry hosting, monitoring tools and ongoing operator hours recur every month. Costs rise with the number of apps, database size, backup retention, the amount of headroom you keep for traffic spikes, and how many people are on call. They fall when you consolidate small apps onto one host, standardize builds, and automate backups and patching. Request a written quote that separates the compute, storage, backup, registry, monitoring and labor lines, so you can compare it fairly with the Fly.io invoice.
How a Fly.io app moves to Dokku in phases
The sequence turns each Fly.io app into a Dokku app on a server you control, with data moved and cut over deliberately and both environments running side by side until the move is proven.
Inventory apps and fly.toml settings
List every Fly.io app and read its fly.toml for build settings, process groups, services and ports, checks, release commands and mounts. Record which Postgres clusters, secrets and volumes each app depends on, and which regions each Machine runs in. Ask your engineering team to confirm any Fly.io feature that is not in the file, such as autoscaling rules or private networking. The step is done when you have one table with a row per app and a column per setting, reviewed by the app owners. This inventory drives every later decision, so gaps found here are cheap to fix.
Choose the scheduler and server layout
Decide whether each app runs on Dokku's default docker-local scheduler on a single server, or on the k3s scheduler across several servers. Dokku's docs describe k3s as requiring a Docker registry, and only the initial Dokku server is the git push target, so note that constraint before choosing it. Map each Fly.io region placement to a server or a deliberate decision to consolidate. Confirm host sizing with the Dokku installation requirements for your operating system. The output is a written layout that names each server, its role and the apps it hosts.
Translate the build to Dokku
Reuse the Dockerfile where the app already has one, so Dokku builds from the same image definition Fly.io used. For apps without a Dockerfile, select a builder: Cloud Native Buildpacks through the pack CLI, or the herokuish builder, and confirm the stack it uses. Map each fly.toml process group to a Procfile entry, and move the release command into the release process type, which Dokku runs like Heroku's Release Phase. The check is a local deploy of each app to a staging Dokku host with the same health endpoints passing.
Provision Postgres and move data
Install the dokku-postgres plugin, create a service for each database, and link it so the app receives DATABASE_URL. Take a logical export from the Fly.io Postgres cluster, then load it with postgres:import on the Dokku service. Verify row counts, sequences and extensions against the source, and test the application against the restored copy. Plan S3-compatible backups from the start, since the plugin supports scheduled backups. The step closes when the restored database passes your application tests.
Move volumes and secrets
Copy any persistent files from Fly.io volumes to storage on the Dokku host or to object storage, and set the application's environment variables with the Dokku config commands. Rotate secrets that were shared outside the secret store during the move. Confirm each mount path the app expects is present on the new host. Record where every secret now lives and who can read it. The step is done when a staging deploy reads all files and secrets correctly and no secret value is stored in the repository.
Cut over DNS and TLS
Lower DNS TTLs in advance, point each domain at the Dokku host, and let the reverse proxy issue certificates. Dokku ships nginx by default with Caddy, HAProxy, OpenResty and Traefik as alternatives, so confirm which proxy your team chose and test certificate issuance for every domain before switching live traffic. Freeze writes on the Fly.io database during the final data sync, then repoint traffic. The output is a verified HTTPS response for each domain from outside your network.
Run both environments in parallel
Keep the Fly.io apps running and warm while the Dokku apps serve production traffic for an agreed observation period. Compare error rates, latency and logs between the two, and watch the database for drift. Keep the Fly.io database as a read copy or stop writes to it so a rollback remains possible. The check is a signed-off list of monitored signals showing that Dokku matches or improves on Fly.io behavior for each app.
Retire Fly.io resources and document rollback
Once the observation period ends, stop Fly.io Machines, delete unused volumes and snapshots, and release IP addresses and certificates you no longer need. Before deleting the last database copy, take a final backup. Document rollback as a data decision: redeploying a previous image is quick, but restoring databases and persistent files needs a tested backup and restore. Coolify's guidance for teams leaving managed platforms makes the same point. The step closes when billing shows no Fly.io charges and the runbook has a tested restore procedure.
What buyers ask before moving from Fly.io to Dokku
How much effort does moving from Fly.io to Dokku take?
The effort depends mostly on how many apps you run, how many use process types beyond a single web process, and how much state sits in Fly.io volumes and Postgres. Dokku deploys with a git push to a dokku remote and builds from a Dockerfile, Cloud Native Buildpacks, or Heroku-style buildpacks, so stateless apps with a Dockerfile are usually the simplest to move. Apps that rely on fly.toml settings such as multiple process groups, release commands, mounts or per-region placement need translation work. Ask any provider to inventory your fly.toml files, volumes and databases before quoting, because that inventory sets most of the scope.
Can our team migrate from Fly.io to Dokku without outside help?
A team with Linux administration experience, Docker knowledge and someone who owns DNS and TLS can do the move internally, especially for a small number of stateless apps. The harder parts are server hardening, proxy configuration, database migration and restore testing, which are easy to underestimate when the platform has been handled by someone else. Dokku's documentation covers installation on supported Ubuntu and Debian releases and needs 1 GB of RAM for the default Docker scheduler, but it does not replace planning for backups and monitoring. Use a written checklist to decide which parts your team owns and which parts you buy.
How long does a Fly.io to Dokku migration usually take?
Duration is set by the number of apps, the amount of data to move and how much testing your release process requires, not by Dokku itself. A few stateless apps with a working Dockerfile can often be redeployed quickly, while stateful services, database cutovers and parallel running take longer because they need a maintenance window and rollback plan. No fixed timeline applies across environments. Ask for a phased plan in writing with a per-app list, a database cutover plan and acceptance criteria, then confirm the schedule against your own change calendar.
What does the team have to run after the move?
After migration, your team owns the server OS patching, Dokku upgrades, certificate renewal through the reverse proxy, application deployments, database maintenance and monitoring. Dokku is still versioned 0.x, and the current release line should be tracked in the project's GitHub releases, so plan a recurring upgrade cycle with testing in staging. Fly.io handled capacity and host maintenance for you, so you also need an on-call route for host failures. Confirm whether a support or upgrade retainer fits your team before the cutover, since that defines who responds when something breaks.
Can Dokku replace Fly.io multi-region placement?
No, not on a single Dokku server. Dokku's default docker-local scheduler runs containers directly on the Dokku host, so every app lives in the location of that server. The k3s scheduler, added in Dokku 0.33.0, can build a multi-server K3s cluster, but it requires a Docker registry, and only the initial Dokku server is configured as the git push target. Teams that need Fly.io-style placement near users in several regions must design that themselves with separate servers, DNS routing and data replication, and should confirm each of those pieces before committing.
How do backups and restores differ between Fly.io and Dokku?
Fly.io volumes and snapshots are managed by the platform, while Dokku leaves backups to you. The official dokku-postgres plugin installs PostgreSQL 18.6 by default, supports exports and imports with postgres:export and postgres:import, and can run one-off or scheduled backups to S3-compatible storage. Persistent files need their own backup job, and a restore should be tested on a separate server before you rely on it. Coolify's guidance for teams leaving managed platforms makes the same point: a rollback can redeploy an application image but does not restore databases or persistent files. Confirm your recovery time and recovery point targets before the cutover.
What does the migration cost in components?
Costs fall into one-time work and recurring charges. One-time costs cover inventory, translation of fly.toml settings, database migration, testing and cutover planning, which you pay either in staff time or through a fixed-scope engagement. Recurring costs include the server or servers, storage, backup destinations, egress and your staff time for upkeep, while Fly.io bills Machines, volumes, snapshots, egress and IP addresses by usage. Ask any quote to separate hardware or cloud instances, backup storage, labor for migration, and ongoing support so you can compare the total against your current invoices.
How DeployOpen helps teams evaluate and move off Fly.io
DeployOpen plans and implements self-hosted open-source platforms inside your own cloud accounts, data centers or air-gapped networks. For Fly.io vs Dokku, we start with a written scope that tests which of your apps can run on a single Dokku host and which need regional Machines or a K3s cluster.
How we scope
- Inventory each Fly.io app's build method, process types, release commands, volumes and Postgres usage before choosing a Dokku layout
- Identify which workloads depend on regional Machine placement, since Dokku has no direct equivalent to that model
- Agree the written scope, deliverables and timelines before any work starts, as most engagements are fixed-scope
- Confirm with your team which Dokku scheduler, builder and Postgres plugin settings match your apps and recovery targets
How we build
- Set up Dokku on servers you control and verify the Dockerfile or Cloud Native Buildpacks builds for each app
- Translate Procfile process types, release steps and scaling rules into Dokku configuration and test them in a staging environment
- Configure the dokku-postgres plugin, including backups to S3-compatible storage, and validate restores before cutover
- Test domains, TLS and the reverse proxy you select, then run security testing on the applications and APIs being moved
How we hand over
- Deliver documentation of the deployment layout, build settings, backup jobs, proxy configuration and rollback steps
- Walk your platform and engineering teams through deploys, scaling, database restores and recovery procedures
- Document what Dokku does not replicate from Fly.io, such as regional placement, so your team can make informed decisions
- Provide evidence and controls that your compliance assessors can review, while independent auditors issue any formal reports
How we support
- Offer ongoing support, upgrades and continuous security testing as a retainer after handover
- Plan Dokku upgrades and proxy or plugin updates as part of the retainer scope, agreed in writing
- Review backup and restore tests on a schedule your team agrees, so recovery stays a verified process
- Quotes follow from the written scope, and DeployOpen does not publish a price list

