Render → Fly.io
Move a Render web service onto Fly.io machines, and move Render Postgres with pg_dump into Fly Managed Postgres. Disks and env vars do not come along with the image.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Render
- Straightforward web services, workers, databases, and static sites cover common architectures
- Managed deployments reduce infrastructure and release-management work
- Regional options, platform depth, and scaling controls are less extensive than larger clouds
- Runtime assumptions, platform services, and pricing models shape the architecture
Fly.io
- Applications and databases can run close to users with container-level control
- Managed deployments reduce infrastructure and release-management work
- Networking, capacity, and operational debugging require more infrastructure skill
- Runtime assumptions, platform services, and pricing models shape the architecture
Fly.io: Applications and databases can run close to users with container-level control. This removes a major source-side concern: Regional options, platform depth, and scaling controls are less extensive than larger clouds.
What you lose: Straightforward web services, workers, databases, and static sites cover common architectures. What you inherit: Networking, capacity, and operational debugging require more infrastructure skill.
Jump to a section
Know the shape of the move.
This timeline assumes
- A small set of Render web services, one Render Postgres database, and optional persistent disks.
- The database is under Fly Managed Postgres limits: 1 TB max, and 500 GB at first create unless you expand after.
- You can reach Render's external database URL and run fly mpg proxy from a laptop.
- The site already documents Render → Railway and Render → Heroku if you want a comparison.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Web service | partial | critical | You deploy the app again on Fly.io. Render's service settings do not become a fly.toml by themselves. | Write the image, port, region, and health check. Deploy to a staging app before DNS. |
| Environment variables | manual | critical | Secrets stay in Render until you set them on Fly.io. A git push does not copy them. | Set Fly secrets from a password manager, not from a screenshot in Slack. Rotate anything that was shared. |
| Postgres data | partial | critical | Fly's Managed Postgres import guide says there is no automated migration. The simple path is pg_dump of the source piped into psql through fly mpg proxy on localhost port 16380. The new cluster's volume must be at least as large as the database or the import fails. | Create the cluster with enough disk. Restore into an empty cluster and compare counts. |
| Extensions and size | partial | critical | Fly says an import fails when the source needs an extension Managed Postgres does not offer. Databases over 1 TB do not fit. Creation starts at 500 GB or less. You expand afterward for anything bigger. pgvector and PostGIS are the third-party extensions Fly lists as supported. | List extensions on Render before you create the cluster. |
| Render backups | manual | high | Paid Render Postgres has point-in-time recovery: 3 days on Hobby, 7 days on Pro or higher. Recovery creates a new instance. You cannot restore to a time within ten minutes of now. Free compute has no recovery. Logical exports are .dir.tar.gz files kept for seven days. pg_dump from the external URL is the copy you can move. | Take a pg_dump you store outside Render before cutover. Do not rely on the seven-day export alone. |
| Persistent disks | manual | critical | A Render disk is not in the Git repo. Uploads and other files need their own copy onto a Fly volume. | Inventory mount paths. Copy files and open one of each type after deploy. |
| Regions | manual | medium | Fly runs the app in the region you set. Render's region does not move with the service. | Pick the region closest to your users and measure latency before DNS. |
| Custom domains and TLS | manual | critical | Certificates are issued on Fly.io after you add the hostname. Render's certificate does not transfer. | Add the domain, lower TTL, and keep the old records written down. |
| Background jobs and cron | partial | high | Render cron jobs and workers are separate services. Fly needs a process or a scheduled machine for each one. | List every schedule. Run each once on staging before you disable Render. |
| Unmanaged Fly Postgres | lost | high | Fly's older unmanaged Postgres getting-started page is archived. Fly says support will not guide you on it, and points new databases at Managed Postgres. | Create a Managed Postgres cluster. Treat 2024 writeups that use unmanaged Postgres as history, not as the current path. |
| Logs and metrics history | lost | medium | Render's log history stays in Render. Fly starts collecting after the new app runs. | Export any log evidence you still need before you suspend Render. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Render web service | Fly app | manual | Set the internal port, region, and VM size in fly.toml or the dashboard. |
| Render env var | Fly secret | manual | Recreate each one. Rotate shared values. |
| Render Postgres external URL | pg_dump source | transform | Dump with --no-owner. Render's example uses -n public. |
| pg_dump stream | Managed Postgres via fly mpg proxy | transform | Proxy localhost:16380. Use the fly-user connection string with that host. |
| Render disk | Fly volume | manual | Copy files. A deploy does not include the disk. |
| Render cron | Fly scheduled machine or process | manual | Run it once on staging. |
| Render custom domain | Fly certificate | manual | Add the hostname before you change DNS. |
| Render region | Fly primary region | manual | Choose it on purpose. It does not copy. |
| Postgres extension | Managed Postgres extension | manual | Stop if you need an extension Fly does not list. |
Make the move recoverable.
Create the source-of-truth backup
Keep a Render dump you can restore, and keep Render running until Fly answers for the domain.
- Write down services, regions, disk paths, cron, and domains.
- Run pg_dump against the external database URL and store it off Render.
- Copy disk contents to a tarball.
- Export any log evidence you need.
- Leave Render's paid database in place so point-in-time recovery still exists during the window.
Proof to capture: The dump restores to a scratch database, and the tarball lists the disk paths you care about.
Database import
Load Render Postgres into a new Fly Managed Postgres cluster.
- Create the cluster with a volume at least as large as the source. Stay under the 500 GB initial cap, then expand if you must.
- Run fly mpg proxy and point the connection string at localhost:16380.
- pg_dump the Render external URL and pipe it into psql, or split the job if the database is large. Fly also mentions pgloader and pgcopydb for harder cases.
- Compare table counts before you change the app's DATABASE_URL.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
App and disk
Boot the service on Fly and put uploaded files on a volume.
- Deploy a staging app with the same start command and port.
- Set secrets.
- Copy the disk tarball onto the volume and open a sample file.
- Point the staging app at Managed Postgres and run one write.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
The volume is smaller than the dump
Fly says the import fails when the new volume is smaller than the database.
- Consequence
- You get a partial cluster and a false sense that the dump is loaded.
- Mitigation
- Size the volume first. Check counts after psql exits.
Stop if: Table counts do not match the dump.
Both hosts write the database
Render still has the old DATABASE_URL while Fly is live.
- Consequence
- Rows split across two databases.
- Mitigation
- One writer. Suspend Render services before the final dump.
Stop if: Render logs show writes after the dump started.
Disk files never left Render
The app boots and the upload directory is empty.
- Consequence
- Users see broken images or missing documents.
- Mitigation
- Copy the disk and request a known file.
Stop if: A file that exists on Render 404s on Fly.
Do the work in this order.
- Days 1 to 2 · inventory
List services and extensions
3 to 6 hours active1 to 2 days elapsedOwner confirms region waiting- List services, cron, disks, and Postgres extensions.
- Check database size against Fly's 500 GB create cap and 1 TB max.
Depends on: Render admin and a Fly org
Stop / go checkpointCreate the cluster?
Go when: Every extension is supported or dropped on purpose.
Stop when: You need an unsupported extension or more than 1 TB.
- Days 2 to 3 · backup
Dump and tarball
2 to 6 hours active1 day elapsedpg_dump waiting- pg_dump to storage you control.
- Tarball the disk.
Depends on: External database URL
Stop / go checkpointRestore on Fly?
Go when: The dump loads in a scratch database.
Stop when: pg_dump errors.
- Days 3 to 7 · pilot
Staging app
6 to 14 hours active2 to 4 days elapsedImport and certificate waiting- fly mpg create and import through the proxy.
- Deploy the app, copy the disk, and hit a staging URL.
Depends on: Dump
Stop / go checkpointCut DNS?
Go when: Counts match and a write succeeds on staging.
Stop when: The import fails the size or extension check.
- Days 7 to 8 · cutover
Final dump and DNS
2 to 6 hours active1 day elapsedDNS waiting- Suspend Render writers.
- Take a final dump and import it.
- Move DNS.
Depends on: Staging passed
Stop / go checkpointFly owns traffic?
Go when: The domain serves Fly and Render is idle.
Stop when: Render still writes.
- Days 8 to 15 · observe
Keep Render recoverable
2 to 4 hours active7 days elapsedReal traffic waiting- Watch errors and disk space.
- Keep the Render database until the window ends.
Depends on: DNS
Stop / go checkpointSuspend Render for good?
Go when: A week of traffic looks like the Render baseline.
Stop when: You need Render point-in-time recovery.
Cut over with a way back.
Cutover
Fly.io serves the app and holds the only new database writes.
- Suspend Render workers and the web service.
- pg_dump Render and import through fly mpg proxy.
- Copy any disk delta.
- Point DNS at Fly and confirm TLS.
- Watch Render logs for a stray writer.
Proof to capture: Counts match, the domain is Fly, and Render shows no new queries.
Rollback
Send traffic back to Render, which still has its data from before the final dump.
- Stop the Fly app from taking writes.
- Restore the previous DNS records.
- If Fly accepted writes, dump those rows before you resume Render.
- Resume the Render service against its original database.
- Place a test request.
Proof to capture: Render serves the domain and its database is the one you did not drop.
- Table counts differ
- An extension is missing
- Uploads 404
- Render still writes after cutover
- TLS or the health check fails
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | Extensions | Compare pg_extension on Render with Fly's supported list. | Every required extension exists on Managed Postgres. | Extension list. | |
V-02Blocking | Table counts | Count rows on both sides after the pilot import. | Counts match for every table in scope. | Count sheet. | |
V-03Blocking | Staging boot | Request the health URL on the Fly staging app. | The process listens on the port in fly.toml and reads Managed Postgres. | HTTP check. | |
V-04Blocking | Known file | Open a file that lived on the Render disk. | The file is on the Fly volume. | File request. | |
V-05Blocking | App config | Boot with Fly secrets and without a copied .env in the image. | The app reads secrets and the image has no secret file. | Boot log. | |
V-06Blocking | One cron run | Trigger each schedule on staging. | One successful run and no Render twin. | Job log. | |
V-07Blocking | TLS | Resolve the hostname and open it. | Fly's certificate is valid. | Certificate. | |
V-08Blocking | Render idle | Read Render logs after suspend. | No new queries or deploys. | Render log. |
Remove the scaffolding safely.
Safe after: Seven days of Fly traffic, with the offsite pg_dump still in storage.
- Keep the pg_dump for the retention period you agreed.
- Revoke the Render external database credential you used for the dump.
- Suspend Render services only after rollback is closed.
- Delete the Render database last. Its logical exports expire in seven days.
- Remove staging Fly apps you no longer need.
- Set a Fly backup you can restore with fly mpg restore, and test it once.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Render: Postgres recovery and backupsAccessed 2026-11-03
- Fly.io: import into Managed PostgresAccessed 2026-11-03
- Fly.io: appsAccessed 2026-11-03
- Fly.io: Managed PostgresAccessed 2026-11-03