# Fly.io

Fly runs your container as one or more **Machines**. Use the [Node adapter](/docs/adapters/node), ship a Dockerfile, and let `auto_stop_machines` scale you to zero when idle.

**Diagram: fly deploy pipeline**

1. **Dockerfile build** (image) - distroless node:24 image
2. **fly deploy** - ship image to Fly
3. **Machines + health checks** - GET /healthz
4. **auto_stop_machines** - scale to zero when idle

Fly runs the container as one or more Machines. Health checks against /healthz gate routing, and auto_stop_machines = stop scales you to zero when traffic drops.

## When to choose Fly

- You want multiple regions cheaply, with anycast routing for free.
- You want a single image that also runs on ECS or Kubernetes elsewhere.
- You want raw TCP without a serverless workaround.

## Server entrypoint

Use the Node adapter and bind to the Fly-provided `PORT`:

```ts
// src/server.ts
import { serve } from "@daloyjs/core/node";
import { app } from "./app.js";

serve(app, {
  port: Number(process.env.PORT ?? 3000),
  hostname: "0.0.0.0",
});
```

## fly.toml

`auto_stop_machines` takes a **string** (`"off"`, `"stop"`, or `"suspend"`) and not a boolean.

```toml
# fly.toml
app = "my-daloy-api"
primary_region = "fra"

[build]

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = "stop"
  auto_start_machines = true
  min_machines_running = 0

  [http_service.concurrency]
    type = "requests"
    soft_limit = 200
    hard_limit = 250

[[http_service.checks]]
  interval = "10s"
  timeout = "2s"
  grace_period = "5s"
  method = "GET"
  path = "/healthz"

[[vm]]
  size = "shared-cpu-1x"
  memory = "256mb"
```

## Dockerfile

```docker
FROM node:24-slim AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM gcr.io/distroless/nodejs24-debian12
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
USER nonroot
EXPOSE 3000
CMD ["dist/server.js"]
```

## Deploy

```bash
brew install flyctl
fly launch --no-deploy
fly secrets set SESSION_SECRET=...
fly deploy
```

## Gotchas

- Set `shutdownTimeoutMs` on the Node adapter to a value smaller than Fly's grace period so in-flight requests drain before the machine is killed.
- Make sure `/healthz` is cheap. The [lifecycle plugin](/docs/security/lifecycle-health) ships a ready-made one.

## See also

- [Deployment overview](/docs/deployment)
- [Node adapter](/docs/adapters/node)

---

Source: https://daloyjs.dev/docs/deployment/fly-io