Skip to main content

Migrate from Fly.io

Map fly.toml processes, ports, checks, mounts and release commands to a Bex render.yaml, and see which Fly.io settings have no Bex equivalent.

Share
Last updated on October 5, 2026
On this page

A fly.toml file configures one Fly.io app: its build, processes, HTTP service, checks, volume mounts and release command. A Bex render.yaml describes managed services, datastores and disks. Most of a fly.toml maps, but Fly's regions, Machine sizes, proxy settings and static-file serving do not, and moving a file moves no data.

Bex is in active development and its upstream project does not yet recommend production workloads. Begin with a non-production rehearsal and check compatibility and required capabilities before proceeding. Compare costs with Fly.io pricing and Bex pricing directly; this guide does not restate either.

Use the converter​

Paste your fly.toml, and optionally the output of fly secrets list, into the Fly.io converter. It is a browser starting point: it drafts a render.yaml by the rules below, lists every element it could not map with the reason, and checks the draft with the render.yaml checker. bex blueprints validate remains the authoritative check. Nothing you paste leaves your browser.

How fly.toml maps to Bex​

fly.tomlBex
[build] dockerfile, or no build settingsDocker runtime (dockerfilePath)
[build] imageImage runtime; the image's own command runs
builder, buildpacksNot mapped: a native runtime when the process commands show one, else Docker
Process the HTTP service targetsWeb service
Other processes, or every process when there is no HTTP serviceBackground worker
Process behind a TCP [[services]] entryPrivate service (pserv); public TCP ports and UDP are not mapped
internal_port (Fly's default is 8080)PORT, unless it is 3000, Bex's injected default
HTTP check with a pathhealthCheckPath
auto_stop_machines, auto_start_machines, min_machines_runningExplained: only free-tier web services sleep when idle
One [[mounts]] entry on one processA disk (paid plan, single instance); initial_size from 10 to 10,000 GB becomes sizeGB
A second mount, a shared mount, a mount on a process kept above one MachineNot mapped, with the reason
[deploy] release_commandpreDeployCommand on the web service
[env], secret names from fly secrets listOne environment group; secrets become sync: false
primary_region, [[vm]], [[statics]], [metrics], [[restart]], swap_size_mb, kill_signal, force_https, concurrencyNot mapped, with the reason

Example​

This is the converter page's Rails sample. It builds a production Dockerfile, prepares the database as a release command, and serves HTTP on port 8080:

toml
app = "ledger"
primary_region = "fra"
kill_signal = "SIGTERM"
kill_timeout = 30
swap_size_mb = 512
console_command = "/rails/bin/rails console"
 
[build]
  dockerfile = "Dockerfile.production"
  build-target = "runtime"
 
  [build.args]
    RUBY_VERSION = "3.3.5"
 
[deploy]
  release_command = "./bin/rails db:prepare"
  strategy = "bluegreen"
 
[env]
  RAILS_ENV = "production"
  RAILS_LOG_TO_STDOUT = "enabled"
  PORT = "8080"
  SECRET_KEY_BASE = "do-not-commit-this-placeholder"
 
[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = "suspend"
  min_machines_running = 1
 
  [http_service.concurrency]
    type = "requests"
    soft_limit = 200
    hard_limit = 250
 
  [[http_service.checks]]
    grace_period = "10s"
    interval = "30s"
    method = "GET"
    timeout = "5s"
    path = "/up"
 
[[statics]]
  guest_path = "/rails/public"
  url_prefix = "/"

Its secrets, as fly secrets list prints them (names and digests, no values):

text
NAME                    DIGEST                  CREATED AT
DATABASE_URL            a1b2c3d4e5f6a7b8        2026-09-01T10:00:00Z
SECRET_KEY_BASE         0f1e2d3c4b5a6978        2026-09-01T10:00:00Z
STRIPE_API_KEY          9988776655443322        2026-09-12T08:30:00Z

The converter drafts this render.yaml from them:

yaml
# Draft render.yaml for Bex, converted from Fly.io by bex.co/tools.
# A starting point, not a migration: replace the placeholders, enter secrets
# in the dashboard, choose plans, and move data, domains and add-on
# credentials separately. Then run `bex blueprints validate`.
services:
  # From fly.toml line 1: app
  # plan: not set; choose one at https://bex.co/pricing
  - type: web
    name: ledger
    runtime: docker
    repo: https://github.com/YOUR-ORG/YOUR-REPO  # replace with your repository
    branch: YOUR-BRANCH  # replace with your deploy branch
    dockerfilePath: Dockerfile.production
    preDeployCommand: ./bin/rails db:prepare
    healthCheckPath: /up
    envVars:
      - key: PORT
        value: "8080"
      - fromGroup: ledger-config
envVarGroups:
  # From fly.toml line 19: env
  - name: ledger-config
    envVars:
      - key: RAILS_ENV
        value: production
      - key: RAILS_LOG_TO_STDOUT
        value: enabled
      - key: SECRET_KEY_BASE
        sync: false  # set this secret in the dashboard
      - key: DATABASE_URL
        sync: false  # set this secret in the dashboard
      - key: STRIPE_API_KEY
        sync: false  # set this secret in the dashboard

Replace the repository and branch placeholders, choose a plan, and enter each sync: false value before you validate it. SECRET_KEY_BASE is in both [env] and the secrets; as on Fly.io the secret wins, so the draft leaves the [env] value out and asks for the secret instead. The converter lists the region, kill and swap settings, console_command, the build target and arguments, force_https, concurrency, the deploy strategy and [[statics]] as not mapped, each with its reason.

Processes, ports and checks​

Without [processes], Fly runs every Machine with the same command; with it, each process group runs its own command (Fly.io configuration reference, checked 2026-10-01). The process [http_service] targets becomes a Bex web service, and the others become background workers.

Fly's internal_port defaults to 8080, and Bex's injected PORT defaults to 3000. Bex does not detect ports, so the draft sets PORT to the Fly port. An HTTP check's path becomes healthCheckPath, which Bex probes with an HTTP GET (health checks). Ports below 1024 are not mapped: tenant containers drop Linux capabilities (Docker deploys).

Fly's release_command runs a one-off task before Machines are created or updated, and fails the deploy on a non-zero exit. The draft makes it the web service's pre-deploy command, which runs in the new image without the service's disk.

Secrets and environment​

Fly's [env] is for non-sensitive values; secrets are set with the secrets command, take precedence over [env], and their values cannot be read back (Fly.io secrets, checked 2026-10-01). The converter therefore takes only secret names and marks them sync: false. Enter each value through Bex's secrets interface.

Volumes and disks​

A Fly volume attaches to one Machine and is not replicated (Fly.io volumes, checked 2026-10-01). One mount on one process becomes a persistent disk: one disk per paid service, a single instance, 10 to 10,000 GB. The disk starts empty; copy files with an application-appropriate export and import.

What does not map​

Fly's auto_stop_machines and min_machines_running let the Fly proxy stop idle Machines. On Bex only free-tier web services sleep when idle, and there is no render.yaml idle field (idle services). Regions, Machine sizes, [[statics]], [metrics], restart policies, swap and kill signals have no render.yaml setting; the converter lists each one. Fly Postgres, Upstash, Tigris, certificates and IP addresses are not in fly.toml at all.

Move data and traffic​

Rehearse the data transfer and the cutover with steps 4 and 5 of Migrate from Render; they apply to any source host. Stop every source writer, including Fly worker processes, before the final transfer. Add your domains through Bex domain setup and use the DNS records it returns, not the ones that pointed at Fly.io.

See Bex vs Fly.io for a product comparison.

Was this page helpful?

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex