Status as of 2026-09-06. This is the design eval, not a ship checklist.
Intent
Use Iroh (iron, P2P QUIC) and Zenoh (brokered zenohd) for Elixir
node membership, then drive Phoenix FLAME runners over that overlay.
Kind (Kubernetes-in-Docker) clustering is out of scope.
Package map
| Package | Role | Keep? |
|---|---|---|
| gale | Phoenix HTTP/3 (Zig NIF, Bandit sidecar) | yes |
| ingot_cluster | Iroh + Zenoh cluster, libcluster strategies, one FLAME module | yes |
| dusk | Zenoh-first cluster (optional Iroh) | yes |
| crucible | Multi-cloud boot (Driver, Gale HTTP) | yes |
| zeiroh | FLAME Iroh/Zenoh backends; launch package: :ingot_cluster | :dusk | :zeiroh | yes |
zeiroh path-depends on sibling ingot_cluster and dusk. Mix still compiles those
path deps even when marked optional: true. It does not fix the
iroh_beam vs zenohex rustler_precompiled pin clash: a host app still
picks one native backend.
What the tests prove
mix test in zeiroh/ (5 cases) and matching ingot/dusk FLAME tests:
- A local Task loop starts.
remote_spawn_monitorruns a 0-arity function on the parent VM.- Overlay processes register (
Zeiroh.Iroh/Zeiroh.Zenoh) as stubs wheniroh_beam/zenohexare absent.
They do not prove remote BEAM boot, FLAME.Pool, or live Iroh/Zenoh
discovery.
What Phoenix FLAME actually requires
FLAME.Backend (flame 0.5):
| Callback | Contract |
|---|---|
init/1 | Merge app env. FLAME passes :terminator_sup. |
remote_boot/1 | Provision or attach a BEAM node. Export FLAME_PARENT. Wait for {ref, {:remote_up, terminator_pid}}. Return {ok, terminator_pid, state}. |
remote_spawn_monitor/2 | Spawn on that remote node (Node.spawn_monitor / terminator), not in the parent VM. |
system_shutdown/0 | Halt the runner (no_return). |
handle_info/2 | Pool messages. |
FLAME.LocalBackend starts FLAME.Terminator under terminator_sup.
FLAME.FlyBackend boots a machine, then the terminator connects back.
Current backends (zeiroh / ingot / dusk)
- Do not declare
@behaviour FLAME.Backend. - Do not start
FLAME.Terminator. - Do not set
FLAME_PARENT. - Do not boot or connect a remote node.
system_shutdown/0returns:ok.- Runner is a local Task — LocalBackend-shaped, not Fly-shaped.
FLAME.Pool / FLAME.call/3 will not drive these the way they drive Fly.
What Iroh and Zenoh can and cannot do
Iroh and Zenoh cannot provision VMs. They can:
- Discover already-running nodes (gossip
node@host, then:net_kernel.connect_node/1). - Later: carry Erlang distribution (custom dist carrier — large project).
Without Fly, Kubernetes, or local Docker boot, the honest product is attach-to-cluster, not elastic FLAME.
libcluster today
IngotCluster.Strategy.Iroh / IngotCluster.Strategy.Zenoh (and dusk counterparts):
GenServertick +Cluster.Strategy.connect_nodes/4.- Peer list is
config[:nodes], static. - They start overlay stubs; they do not subscribe to Iroh tickets or Zenoh keys for live membership.
Useful as a strategy shape. Not a mesh yet.
Recommended order (do this before a fourth Hex package)
- libcluster live membership — put/get
node@hoston a Zenoh key (and Iroh gossip wheniroh_beamis present). Static:nodesonly in tests. - FLAME attach mode —
@behaviour FLAME.Backend;remote_bootpicks a connected node or falls back toFLAME.LocalBackend+ terminator when there is no remote.FLAME.call/3works in-process. - FLAME boot mode — wrap Fly / K8s / Docker; Iroh/Zenoh only for membership after boot.
- One backend module —
IngotCluster.FLAME.Backendwithoverlay: :iroh | :zenoh | :both. Zeiroh is the FLAME-facing package (Zeiroh.FLAME.Iroh/.Zenoh/.Backend) and delegates cluster to ingot_cluster/dusk. Crucible isprovisioner:under that backend.
Launch (current, local overlay)
{:zeiroh, path: "../zeiroh"}
{:flame, "~> 0.5"}
{Zeiroh, overlay: :both, live: false}
{Zeiroh, package: :ingot_cluster, iroh: true, zenoh: [live: false]}
{Zeiroh, package: :dusk, live: false}
config :flame, :backend, {Zeiroh.FLAME.Backend, overlay: :both, live: false}
# or {Zeiroh.FLAME.Iroh, alpns: ["zeiroh/flame"]}
# or {Zeiroh.FLAME.Zenoh, connect: "tcp/127.0.0.1:7447"}live: false is the supported path until zenohd / iroh_beam are wired.
Cannot provision VMs
Iroh and Zenoh are transports. Relays and zenohd do not create Fly
machines or pods. Smooth scaling is provisioner + overlay, not overlay
alone. Plan and tool map: SCALING.md.
Decision
Keep all five: gale (HTTP), ingot (Iroh+Zenoh cluster), dusk (Zenoh-first), zeiroh (FLAME overlay + launch), crucible (boot). Zeiroh is part of the line. Limits above are implementation status, not a reason to drop the package.