Engine on Fly.io
Fly.io runs the engine image as a long-lived container, a Machine. Fly bills by usage, and new
accounts get a short trial rather than a free plan. A Machine with auto_stop_machines stops
when idle and starts on the next request.
Before you start
Section titled “Before you start”- A Fly.io account and the
flyCLI signed in. - A Postgres, MySQL or SQL Server database the Machine can reach. On Supabase or Neon, use the connection string each page recommends.
Deploy
Section titled “Deploy”Save this as fly.toml:
app = "your-lyeve-engine"primary_region = "iad"
[build] image = "ghcr.io/lyeve-labs/lyeve-core:latest"
# Content API: /api/v1 and /.well-known/jwks.json[[services]] internal_port = 3002 protocol = "tcp"
[[services.ports]] port = 443 handlers = ["tls", "http"]
[[services.http_checks]] interval = "15s" timeout = "2s" grace_period = "20s" method = "get" path = "/readyz"
# Admin API: /api/admin[[services]] internal_port = 3001 protocol = "tcp"
[[services.ports]] port = 8443 handlers = ["tls", "http"]
[[services.http_checks]] interval = "15s" timeout = "2s" grace_period = "20s" method = "get" path = "/readyz"The engine listens on 3001 for the Admin API and 3002 for the Content API, and the image
already binds both on all interfaces. Two services cannot share external port 443 in one app,
so the Content API takes 443 and the Admin API takes 8443. If nothing outside Fly needs the
Admin API, drop its [[services]] block and reach port 3001 over Fly's private network.
Run one Machine. A second Machine signs with a key of its own and refuses the first one's
tokens. Pin a release tag instead of latest in production. See
Docker images.
Secrets
Section titled “Secrets”Set every setting with fly secrets set rather than in [env]:
fly secrets set \ DATABASE_URL="postgres://<user>:<password>@<host>:5432/<dbname>?sslmode=require" \ JWT_SECRET="$(openssl rand -hex 32)" \ ENCRYPTION_KEY="$(openssl rand -hex 32)" \ LYEVE_AUDIT_HMAC_KEY="$(openssl rand -hex 32)" \ SECURE_COOKIE=true \ RATE_LIMIT_RPS=100 \ CORS_ORIGINS="https://your-app.example.com"Generate ENCRYPTION_KEY once and keep it. A new value makes stored secrets, such as MFA
seeds, unreadable. Production refuses to start without these values.
Configuration lists every check, and
every other variable. Add LYEVE_LICENSE_KEY for paid features.
Database
Section titled “Database”Point DATABASE_URL at your database. The engine reads the type from the scheme, as
supported databases shows. Use sslmode=require for a
PostgreSQL database outside Fly's private network, or the engine logs a warning on every start.
Health checks
Section titled “Health checks”The checks in fly.toml use /readyz. Both listeners serve /healthz, /readyz and
/startup without authentication, and over plain HTTP even with SECURE_COOKIE=true. Do not
use /api/admin/health or /api/v1/health: they need authentication, so a check gets 401 and
marks a healthy Machine down. Health endpoints says what each one
checks.
Verify
Section titled “Verify”fly deployfly statuscurl https://your-lyeve-engine.fly.dev/.well-known/jwks.jsoncurl -6 https://your-lyeve-engine.fly.dev:8443/api/admin/setupThe first curl lists the engine's public signing key. The second goes over IPv6, because port
8443 does not answer on a shared IPv4 address, and a new install answers
{"setup_required":true,"token_source":"log"}. The setup token is the one-time setup_token in
fly logs, or LYEVE_SETUP_TOKEN when you set it. The
quickstart continues from step 2.
- Free-tier stack: the $0 recipe and its limits.
- Production checklist: what to confirm before real traffic.