Leaving Fly.io After the Free Tier: Secrets, Volumes, and the Dockerfile You Already Have
Fly.io's free allowance is gone: new organisations get a trial of two hours of compute or seven days, whichever ends first, and then everything is pay-as-you-go. That is not a scandal, Fly's smallest machine is about $2 a month, but it changes who Fly is for. This guide is for people whose side project is on Fly and who would rather it cost nothing while idle. It is short on drama and long on the specific things that differ: secrets you cannot export, volumes you cannot bring, and a region model you will not miss.
Fly's pricing, in the numbers that matter
- Trial: 2 hours of compute or 7 days. Then pay-as-you-go with no minimum.
- Smallest machine (shared-cpu-1x, 256 MB): about $2.02 a month if it runs all month; billed per second, so a stopped machine costs nothing for compute.
- Volumes $0.15 per GB-month of provisioned size, billed whether or not the machine is running; a dedicated IPv4 is $2 a month; outbound bandwidth from $0.02 per GB.
- Machines can auto-stop when idle and auto-start on a request, which is Fly's version of sleeping.
Stay on Fly if you use what it is good at: multiple regions, machines you control at the API level, or a workload that needs more than 512 MB and a quarter of a CPU. A $2 to $5 monthly bill for that is fair. Move if the app is a single small service you visit occasionally and you would like the idle bill to be zero.
What maps to what
| On Fly | On a container host (SnapDeploy) |
|---|---|
| Dockerfile (most Fly apps already have one) | The same Dockerfile, unchanged; the port is read from EXPOSE |
[http_service] internal_port | EXPOSE in the Dockerfile, or the container's port setting; read PORT in the app to be safe |
auto_stop_machines / auto_start_machines | Built in on the free tier: sleep after 15 minutes without traffic, wake in about a minute with a wake page; Always-On ($12/month) never sleeps |
[env] block in fly.toml | Environment variables in the container settings |
fly secrets | Also environment variables; see below, they cannot be read back from Fly |
[mounts] volumes | No volumes on containers; use a managed database add-on or object storage |
| Fly Postgres / Managed Postgres | PostgreSQL Mini ($29/month), a $1 DB Sprint Pack for 12 hours, or a free external Postgres such as Neon |
| Regions, private networking (6PN), machines API | One region; containers talk over public HTTPS URLs; no equivalent to the machines API |
| Custom domain with a Fly certificate | Always-On only; free containers live on containers.snapdeploy.app |
Step 1: the Dockerfile is probably done
Fly deploys from a Dockerfile when the repository has one, and fly launch writes one for common frameworks, so most apps arrive with a working file. Check three things: the app listens on 0.0.0.0 and on PORT (Fly apps often hard-code the internal_port); nothing in the image depends on a mounted volume path such as /data; and there is no fly.toml-only behaviour the app expects, like a release command. A release command becomes the first part of the container's CMD (for example alembic upgrade head && …).
Step 2: secrets, the one-way door
Fly secrets are write-only: fly secrets list shows names and digests, never values. If you did not keep the values elsewhere, you have two options: read them from inside a running machine (fly ssh console, then printenv), or rotate them at the source (a new API key from the provider) and set the new value on both platforms during the cut-over. Rotating is usually the better answer; it also means the old platform holds nothing valid once you leave. Plain [env] values are in fly.toml and copy straight across.
Step 3: the database
Fly Postgres is reachable from your machine through a proxy, which is enough for a dump:
fly proxy 15432:5432 -a my-db-app # in one terminal
pg_dump --no-owner --no-privileges "postgres://postgres:PASSWORD@localhost:15432/app" > backup.sql
psql "$NEW_DATABASE_URL" < backup.sqlRun the app against the new database for a day before deleting the Fly database and its volume; the volume is what keeps billing if you forget it.
Step 4: volumes, if you have one
This is the only step with real work. A volume on Fly usually holds one of three things: a SQLite database (move it to Postgres, or accept that the container version is disposable), uploaded files (copy them to an S3-compatible bucket with fly ssh sftp to pull them out, then point the app at the bucket), or a cache (delete it). Container filesystems on SnapDeploy are ephemeral by design; the managed databases have persistent storage, the containers do not.
Step 5: deploy and cut over
- Push the repository. Containers → Deploy from GitHub → pick it; add the environment variables and secrets; attach or paste the database.
- Deploy (a few minutes), test on the
containers.snapdeploy.appURL, then wait 20 idle minutes and load it once more to see the wake page and confirm the app comes back cleanly. - If the app had a custom domain on Fly, either take Always-On and point the domain's A record at the address in the container's domain settings, or keep the domain on Fly a little longer.
- Scale the Fly app to zero machines (
fly scale count 0) for a week before destroying it. Stopped machines cost nothing for compute; only the volume and any dedicated IPv4 keep billing.
Test the image locally first
docker build -t app .
docker run --rm -p 8080:8080 -e PORT=8080 --env-file .env.local app
curl -i http://localhost:8080/If it needs a path from [mounts] to exist, you will see it here: the app crashes on start looking for /data. Fix that before the first deploy.
Where free stops on the other side
- 4 containers, 512 MB and 0.25 vCPU each, 100 container-hours a month counted while awake. Fly's 256 MB machine is smaller but always yours.
- Sleep after 15 minutes without traffic, about a minute to wake. Fly's auto-stop is configurable; ours is not.
- No volumes, one region, no private network between containers, custom domains only on Always-On.
Sources: Fly.io pricing and billing pages as of September 2026 (trial, machine, volume and bandwidth prices); SnapDeploy numbers from the free tier docs and add-on pricing.