Fly.io
`mandu deploy --target=fly` emits a `fly.toml` + `Dockerfile` pair. With `--execute` + `flyctl ≥ 0.1.0` installed, Mandu invokes `fly deploy` on your behalf.
On this page
Fly.io
mandu deploy --target=fly emits a fly.toml and the same
multi-stage Dockerfile that ships with --target=docker. With
--execute, Mandu invokes fly deploy on your behalf — with the
provider token read from Bun.secrets so it never appears on argv or
in logs.
# Emit artifacts only
mandu deploy --target=fly
# Set up the API token
mandu deploy --target=fly --set-secret FLY_API_TOKEN=fo1_...
# First-time initialization
fly launch --copy-config
# Full deploy (requires flyctl + token)
mandu deploy --target=fly --execute
Prerequisites
# Install flyctl (macOS / Linux)
curl -L https://fly.io/install.sh | sh
# Windows
iwr https://fly.io/install.ps1 -useb | iex
# Log in (once)
fly auth login
Minimum version: flyctl 0.1.0. Older versions exit with CLI_E206 and a clear upgrade hint.
Emitted files
.
├── Dockerfile
├── .dockerignore
└── fly.toml
The fly.toml
# fly.toml
app = "my-mandu-app" # derived from package.json name
primary_region = "iad" # override via flyctl login / --region
[build]
# Uses the same Dockerfile as --target=docker
[env]
PORT = "3333"
NODE_ENV = "production"
[[services]]
protocol = "tcp"
internal_port = 3333
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
[[services.ports]]
port = 80
handlers = ["http"]
force_https = true
[[services.ports]]
port = 443
handlers = ["tls", "http"]
[services.concurrency]
type = "requests"
hard_limit = 250
soft_limit = 200
[[vm]]
cpu_kind = "shared"
cpus = 1
memory_mb = 512
auto_stop_machines = true scales a Mandu app to zero when idle —
cold-start budget is ~850ms on Fly's shared-CPU tier, comfortable for
a default deploy.
First deploy
Fly expects a one-time fly launch to create the app slot:
# If you already have fly.toml from `mandu deploy`:
fly launch --copy-config --no-deploy
# Then deploy via Mandu
mandu deploy --target=fly --execute
Alternatively you can skip Mandu's --execute and run fly deploy
directly — the fly.toml and Dockerfile are standard.
Required secrets
| Name | Purpose | Store |
|---|---|---|
FLY_API_TOKEN |
flyctl auth for non-interactive deploys | Bun.secrets → OS keychain |
Configure per-app secrets (DB URL, session secrets) with flyctl, not
mandu deploy --set-secret:
# App-visible env vars at runtime
fly secrets set DATABASE_URL="postgres://..." -a my-mandu-app
fly secrets set SESSION_SECRET="$(openssl rand -hex 32)" -a my-mandu-app
fly secrets set stores values inside Fly's control plane, encrypted
at rest. mandu deploy --set-secret is only for Mandu-side secrets
(the FLY_API_TOKEN used to authenticate the deploy command itself).
Multi-region
Add regions with flyctl:
fly regions add fra sin # Frankfurt + Singapore
fly scale count 3 # 3 machines across regions
Mandu's fly.toml uses the flyctl-configured primary_region. If you
need declarative multi-region pinning, edit fly.toml directly — the
adapter preserves hand edits on subsequent mandu deploy runs.
Health checks
Fly auto-wires health checks against internal_port. The emitted
fly.toml uses a TCP check by default. For HTTP, add:
[[services.http_checks]]
interval = "10s"
timeout = "2s"
method = "get"
path = "/healthz"
Mandu exposes /healthz by default.
Logs + monitoring
# Tail live logs
fly logs -a my-mandu-app
# Open the metrics dashboard
fly dashboard metrics
Common errors
CLI_E205: required provider CLI flyctl is missing — install
with curl -L https://fly.io/install.sh | sh.
CLI_E206: flyctl 0.0.x is older than required minimum 0.1.0 —
run fly version update.
CLI_E207: required secret FLY_API_TOKEN is not present — run
mandu deploy --target=fly --set-secret FLY_API_TOKEN=fo1_....
Deploy succeeds but 503 on request — check fly logs. Usually a
missing app secret (DATABASE_URL, SESSION_SECRET). Set with
fly secrets set.
🤖 Agent Prompt
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/fly to my project.
Summary of the page:
Fly adapter: uses the same non-root Dockerfile as Docker adapter, plus a fly.toml tuned for Mandu (port 3333, auto-stop, auto-start). Secret: `FLY_API_TOKEN` — read from OS keychain, never passed on argv. `--execute` requires flyctl.
Required invariants — must hold after your changes:
- Uses the same multi-stage Dockerfile as `--target=docker`
- Required secret: `FLY_API_TOKEN` — stored in Bun.secrets / OS keychain
- Minimum flyctl version: 0.1.0
- `internal_port = 3333`, `auto_stop_machines = true`, `auto_start_machines = true` by default
- Multi-region: default primary region from flyctl login — override with `primary_region`
Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.
Related
- Docker — Fly uses the same Dockerfile.
- Deploy index — full adapter matrix.
For Agents
{
"schema": "mandu.deploy.fly/v0.24",
"command": "mandu deploy --target=fly",
"artifacts": ["Dockerfile", "fly.toml", ".dockerignore"],
"provider_cli": { "binary": "flyctl", "min_version": "0.1.0" },
"secrets_mandu_side": ["FLY_API_TOKEN"],
"secrets_app_side": "via `fly secrets set`, NOT `mandu deploy --set-secret`",
"fly_toml_defaults": {
"internal_port": 3333,
"auto_stop_machines": true,
"auto_start_machines": true,
"min_machines_running": 0
},
"rules": [
"Run `fly launch --copy-config --no-deploy` once before first `--execute`",
"App-runtime secrets go via `fly secrets set`, NOT `--set-secret`",
"`--execute` requires flyctl installed and authenticated"
]
}For Agents
Fly adapter: uses the same non-root Dockerfile as Docker adapter, plus a fly.toml tuned for Mandu (port 3333, auto-stop, auto-start). Secret: `FLY_API_TOKEN` — read from OS keychain, never passed on argv. `--execute` requires flyctl.
- Uses the same multi-stage Dockerfile as `--target=docker`
- Required secret: `FLY_API_TOKEN` — stored in Bun.secrets / OS keychain
- Minimum flyctl version: 0.1.0
- `internal_port = 3333`, `auto_stop_machines = true`, `auto_start_machines = true` by default
- Multi-region: default primary region from flyctl login — override with `primary_region`