deplaneDocs Dashboard

Moving from Fly.io

Move a Fly app to Deplane with one command. deplane import fly reads the app on Fly.io and the fly.toml in your code, then shows what moves and what stays. It then creates the Deplane app with its variables, workers, cron jobs and release command. Fly.io keeps serving your users until you switch.

The command runs on your own computer with your Fly.io token, which never leaves it. It only reads Fly.io: nothing there is changed. The command and its flags are in the CLI reference, and every error has a fix in Troubleshooting and error codes. It works like Moving from Heroku, which explains the switch-over in more detail.

Before you start

  1. Install the Deplane CLI and run deplane login.
  2. Have the app's code in a folder, with its fly.toml: a checkout of the repository you deploy with fly deploy.
  3. Have flyctl installed and logged in, to create a read-only token.

The Fly.io token. Create a read-only token that expires soon, and pipe it straight in:

fly tokens create readonly --expiry 1h | deplane import fly my-app --check

fly tokens create readonly makes a token that can only read one organization (add --org ORG for another one). The CLI sends it as Fly.io's docs say, Authorization: Bearer FlyV1 fm2_..., prefix and all. You can also paste a token at the hidden prompt, or set FLY_API_TOKEN. A piped token wins over FLY_API_TOKEN, which flyctl uses too. There is no flag for the token. The CLI says which one it used, never the token itself. It is never written to disk, never printed, left out of --debug, and never sent to Deplane. Revoke it when you are done: fly tokens list, then fly tokens revoke ID.

Secrets

Fly.io never gives secret values back: its API and fly secrets list show only names and digests. So the import cannot copy your secrets. It lists them by name instead. Then it creates the app without deploying it, since the app would start without them, and the next steps show what to set:

deplane secrets set DATABASE_URL=... SECRET_KEY_BASE=... --app my-app
cd my-app && deplane launch --app my-app   # deploys the app and writes deplane.toml

To see the values, run fly ssh console -a my-app -C env on a running Machine. It prints the Machine's environment, secrets included, in your own terminal. Deplane never runs it for you. The variables in fly.toml's [env] are not secret, and they are copied.

Check first

$ fly tokens create readonly --expiry 1h | deplane import fly shop --check
Fly app shop (org acme, 4 Machine(s)) → Deplane app shop

Code
  ✓ Deploys /home/me/shop with your Dockerfile: Detected a Dockerfile, port 8080.

Processes
  ✓ web: process group app, 2 Machine(s) of 1 shared CPU(s), 512 MB on Fly.io → 2 small instance(s).
  ✓ release_command → release command `bin/rails db:migrate`. ...
  ✓ worker (process group): 1 Machine(s) of 1 shared CPU(s), 1 GB on Fly.io → worker worker, 1 medium instance(s): `bundle exec sidekiq`.
  ⚠ Machine nightly (scheduled daily) → cron job nightly, `@daily` (UTC), on a small instance: `bin/rails report`. ...
  ⚠ Fly.io mounts the volume data at /data: its files are not copied. ...

Data
  ⚠ DATABASE_URL is a Fly.io secret, so its value (and the database it names) cannot be read: no database is copied. ...

Config vars
  ✓ 2 copied as secrets: RAILS_ENV, RAILS_LOG_TO_STDOUT.
  ⚠ Not copied, Fly.io never gives secret values through its API (only names and digests): DATABASE_URL, SECRET_KEY_BASE. ...

Domains
  ⚠ www.shop.example points at Fly.io (shop.fly.dev). It moves when you switch ...

Plan
  ✓ Fits your Team plan: after the import the team uses 4 of 40 instances and 2.5 GB of 16 GB memory.

Nothing was changed (--check). Run the command again without --check to import.

✓ is what moves as it is, ⚠ is what you should know or do yourself, and ✗ is what stops the import. --check --json prints the same report as one JSON document; secrets_to_set lists the secrets to set, and deploys says whether the import deploys. Then run the command again without --check (in a terminal it asks first; from a script, add --yes). The flags are the same as for Heroku: --source DIR, --name NAME, --check, --yes.

What moves

On Fly.ioOn Deplane
fly.tomlRead from the folder you deploy (--source). A fly.toml for another app is a ✗. Without one, the processes and ports are read from the app's Machines.
The process group that serves HTTP: [http_service], or [[services]] with a port that has the http handler (app by default)The app's web process. Instances: its number of Machines. Railpack starts your Procfile's web line or your Dockerfile's CMD; the report warns when that differs from the group's command.
Other process groups ([processes]) with MachinesWorkers, with the same command and as many instances as running Machines. Standby Machines (the stopped spare fly deploy adds to a group without services) are not instances: Deplane restarts a failed instance itself. A group without Machines, or with a command Deplane cannot take (more than one line, or over 1000 characters), is listed, not created.
Scheduled Machines (fly machine run --schedule) of the app's own imageCron jobs at @hourly, @daily, @weekly or @monthly. Fly.io starts them at some time in the interval; Deplane at its start, in UTC.
Machine sizesBy memory: up to 512 MB small, 1 GB medium, 2 GB large. Larger: large, with a warning that Deplane has less memory (2 GB at most). See Limits.
[deploy] release_commandThe release command.
internal_portDeplane sends traffic to the port it detects and sets PORT; the report warns when that differs from internal_port.
[env]Secrets. Not copied: FLY_*, PRIMARY_REGION and PORT. Values that name Fly.io's private network (.internal, .flycast) are copied with a warning.
SecretsListed by name, not copied: see Secrets.
Certificates (custom domains)Listed, and moved at the switch.
The codeYour folder, built by Railpack, or by your Dockerfile if it has one at its root.

Postgres

No database is copied, because Fly.io gives no secret values: the CLI cannot read DATABASE_URL. The report says what it found:

Redis. If REDIS_URL is a secret (Upstash Redis on Fly.io sets one), no Redis is created. Create one with deplane redis create APP-redis and deplane redis attach APP-redis --app APP; it starts empty. Or set REDIS_URL to a Redis that Deplane can reach.

What does not move

Switch over

When the Deplane app works:

  1. Set the secrets and deploy, as the next steps of the import say. Test the app at its Deplane address.
  2. Workers and cron jobs run on both platforms once the app is deployed. If workers must not run twice (queues, scheduled emails), stop them until you switch: deplane processes scale NAME 0 --app APP. A cron job cannot be paused: remove it (deplane processes remove NAME --app APP) and add it again at the switch, or destroy the Fly.io scheduled Machine first.
  3. Before the day: run deplane domains add HOSTNAME --app APP for each custom domain and add the TXT record it shows.
  4. Move the database, if it moves (see Postgres), and point the app at it with deplane db attach.
  5. Switch DNS to the target deplane domains add showed, and wait for the certificate (deplane domains check HOSTNAME --app APP).
  6. Scale the Fly app to zero (fly scale count 0 --app APP for each process group), then destroy it with fly apps destroy APP once the app has run well on Deplane.
  7. Revoke the Fly.io token: fly tokens list, then fly tokens revoke ID.

Limits

The Heroku import's limits apply: 10 workers and cron jobs per app, sizes up to 2 GB, one disk of 5 GB. Secret values and databases are not copied. The import is built on Fly.io's public Machines API; tell support@deplane.io if Fly.io answers something the CLI does not expect.

This page is also available as Markdown. AI agents can start at /llms.txt.