Skip to content

Machines

A Fly.Machine is a Firecracker VM running a container image. Use it when you already have an image.

The parent is an App. Pin a region, an image, and a guest:

const web = yield* Fly.Machine("Web", {
app: Site,
region: "iad",
image: "nginx:alpine",
guest: { cpuKind: "shared", cpus: 1, memoryMb: 256 },
services: [
{
protocol: "tcp",
internalPort: 80,
ports: [
{ port: 80, handlers: ["http"], forceHttps: true },
{ port: 443, handlers: ["tls", "http"] },
],
},
],
});

region replaces on change. image updates in place by default; the opt-in blue/green strategy prepares replacement Machines instead. Guest defaults to shared-cpu 1× / 256 MB. Port 443 with tls is what makes https://{app}.fly.dev answer. Omit services (or pass []) for a worker that should not be reachable from the internet.

{app}.fly.dev over IPv4 still needs an IpAssignment on the parent App.

Use containers for a group that shares one Machine. Each named container has its own image and can set cmd, entrypoint, env, startup dependsOn, and Pilot healthChecks. Use cmd and entrypoint to override startup; container exec overrides are not supported. Existing single-image init.exec is unchanged. Check intervals and timeouts are numeric seconds. A dependency condition can be started, healthy, or exited_successfully.

const preview = yield* Fly.Machine("Preview", {
app: Site,
region: "iad",
guest: { cpus: 1, memoryMb: 1024 },
containers: [
{ name: "api", image: apiImage, healthChecks: [{ http: { port: 3000, path: "/health" } }] },
{ name: "worker", image: workerImage, dependsOn: [{ name: "api", condition: "healthy" }] },
],
});

count duplicates the entire group. A rolling change to one container can restart the whole group; attached volumes stay with their Machine. Machine env and bindings remain shared, while container env is passed to Fly as a per-container override. Configure Machine or service readiness checks for every critical component before treating a deploy as ready: Pilot container checks and dependencies control startup, not Alchemy’s rollout readiness. Blue/green also supports named containers when every image is an immutable repository@sha256: digest and no volumes are attached. It replaces the whole group and can overlap old and new workers during promotion.

If a rolling update is interrupted, Fly may have accepted the update even though the CLI reported a failure. Wait for the in-progress Machine update to settle, then rerun the same desired deployment. Reconciliation reuses the owned Machine and its attached volumes.

A Machine resource runs one VM by default. Set count to manage several replicas together, or declare separate resources for Machines with different configuration. Fly’s proxy load-balances published services across them.

const web1 = yield* Fly.Machine("Web1", {
app: Site,
region: "iad",
image: "nginx:alpine",
services: [
{
protocol: "tcp",
internalPort: 80,
ports: [
{ port: 80, handlers: ["http"], forceHttps: true },
{ port: 443, handlers: ["tls", "http"] },
],
},
],
});
const web2 = yield* Fly.Machine("Web2", {
app: Site,
region: "iad",
image: "nginx:alpine",
services: [
{
protocol: "tcp",
internalPort: 80,
ports: [
{ port: 80, handlers: ["http"], forceHttps: true },
{ port: 443, handlers: ["tls", "http"] },
],
},
],
});

Both Fly.Machine and Service support count. With the default rolling strategy, replicas within one resource update sequentially; separate resources do not share that ordering. Blue/green prepares the desired replacement topology before old retirement.

count is provisioned capacity, not always-running capacity. With autostop: "stop" or "suspend", replicas may be idle. autostart allows requests to wake them, and minMachinesRunning controls Fly’s running floor. Blue/green checks a running representative plus the required floor without permanently waking all idle replicas. A replacement does not inherit suspended RAM. See idle-capacity semantics.

Remove a Machine from the stack. The next deploy deletes it.

const web1 = yield* Fly.Machine("Web1", {
app: Site,
region: "iad",
image: "nginx:alpine",
});
const web2 = yield* Fly.Machine("Web2", {
app: Site,
region: "iad",
image: "nginx:alpine",
});
const worker = yield* Fly.Machine("Worker", {
app: Site,
region: "iad",
image: "my-image:tag",
env: { LOG_LEVEL: "info" },
restart: { policy: "always" },
});

Alchemy stamps alchemy.stack / alchemy.stage / alchemy.id / alchemy.type onto config.metadata so list() can find owned Machines. Fly injects App secrets as env vars on the Machine. minSecretsVersion requires at least that secret version; it is a floor, not an immutable snapshot. Machine leases do not serialize standalone Fly.Secret, other Services, or runtime vault writers. A later writer can advance the shared App vault. Set an explicit deployment input or version floor when an out-of-band rotation requires a new rollout; secret changes are not automatically watched.

Pass disks as mounts. Alchemy creates a Volume in the Machine’s app and region. A Volume attaches to one Machine.

const box = yield* Fly.Machine("Box", {
app: Site,
region: "iad",
image: "nginx:alpine",
mounts: [{ path: "/data", sizeGb: 1 }],
});

From a Service, prefer MountVolume so the path is part of the binding graph.

For replicas required to run, reconcile waits until the Machine is started (waitMachine, bounded). It then waits for configured readiness checks. With default rolling updates, this happens before the next replica. The default readiness budget is 60 seconds; set deploy.healthTimeout for a longer bounded wait. A failed wait stops the rollout with Fly.ReplicaChecksNotPassing and the last observed check names, statuses, and output. Later replicas remain unchanged; earlier updates are not automatically rolled back. The default rolling strategy updates in place, so a single replica can be unavailable while it updates. Set deploy: { strategy: "bluegreen" } to prepare checked replacement Machines before retirement.

For rolling, skipLaunch: true creates or updates config without starting or checking it. By default, delete force-destroys and waits until gone. Setting shutdown or enabling blue/green instead cordons reachable predecessors, signals each with its own persisted shutdown policy, waits for it to stop, and verifies deletion. A new generation’s shorter timeout must not shorten the old process’s grace window.

A raw image must handle signals, stop accepting work, and drain requests or jobs. These props do not install the Effect runtime in it. A genuinely unreachable host may require force-deleting an owned stateless predecessor once its replacement is ready. This cannot prove the old work drained. See the deployment and recovery limits.

Alchemy uses native Machine leases. It reports contention rather than stealing a lease. These leases do not lock the whole App or serialize every first deployment. Serialize CI deployments to the same resource. LocalState is not a cross-process lock, and App deletion has no Machine-lease precondition.

autoDestroy: true removes a Machine when its process exits. Blue/green requires a persistent process and rejects this option, explicit skipLaunch, attached volumes, and missing readiness checks. It reports unsupported settings rather than silently switching to rolling. Stop and suspend are supported through the idle-capacity rules.

Prefer a Service when the program is Effect. A Service is effectful, supports bindings, and scales with count. Alchemy builds and pushes the image. Prefer a Sprite when the sandbox can hibernate — no parent App, no Docker image.

export default class Api extends Fly.Service<Api>()(
"Api",
{ app: Site, main: import.meta.url, region: "iad", count: 3, port: 3000 },
Effect.gen(function* () {
return {
fetch: Effect.succeed(HttpServerResponse.text("hello")),
};
}),
) {}