Migrate from Fly.io

Fly's mental model — "lightweight VMs scheduled to regions, configured by a TOML file" — is closer to ours than any other cloud's. Migration is largely a translation between fly.toml and invisible.hcl.

fly.tomlinvisible.hcl

# fly.toml
app = "myapp"
primary_region = "ord"

[build]
  image = "ghcr.io/me/myapp:1.0"

[[services]]
  internal_port = 8080
  protocol = "tcp"
  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]

[env]
  PORT = "8080"

[[vm]]
  cpu_kind = "shared"
  cpus = 1
  memory_mb = 256
# invisible.hcl
workload "myapp" {
  image  = "ghcr.io/me/myapp:1.0"
  region = "us-east"        # or "auto" for carbon-aware
  cpu    = "1 vCPU"
  memory = "256 MB"
  env = {
    PORT = "8080"
  }
  scale {
    min = 1
    max = 10
  }
}

route "myapp.example.com" {
  to = workload.myapp
  https = true
}

Service map

FlyJoule Cloud
Fly MachinesCompute
Fly Postgres (Postgres on Fly Machines)JouleDB
Fly Tigris (R2-shape)Object Store
Fly VolumesObject Store (large) or Compute persistent volume (small + per-workload)
Fly Anycast / global routingBuilt-in mesh routing
Fly internal DNS (.internal)Built-in mesh service DNS

Multi-region

Fly's [[vm]] arrays + fly scale count --region become a simpler thing in our model: declare the workload once with region = "auto", and the carbon-aware scheduler places instances. Override with explicit region:

workload "api" {
  image  = "ghcr.io/me/api:1.0"
  region = ["eu-fi", "us-east", "ap-northeast-1"]  # explicit pin
}

Volumes

Fly Volumes attach a per-machine SSD. We don't do per-machine persistent volumes by default — the mental model on the mesh is "stateless workloads, state in JouleDB or Object Store". If you have a workload that really needs a per-instance disk (Postgres outside JouleDB, ZFS-y workloads), tell us; we have a private-beta primitive for it.

What you keep

What you gain