Fly.io
Both apps/api and apps/dashboard ship a fly.toml. The API’s toml
declares a release_command that runs
pnpm --filter @carbon/database migrate:apply, so migrations apply
automatically on every deploy — do not run them by hand.
1. Install flyctl
Section titled “1. Install flyctl”curl -L https://fly.io/install.sh | shfly auth login2. Provision Postgres and Redis
Section titled “2. Provision Postgres and Redis”Pick one path.
Fly-native
fly postgres create --name carbon-dbfly redis create --name carbon-redisfly postgres attach carbon-db --app carbon-apiNeon + Upstash
fly secrets set DATABASE_URL="postgres://…" REDIS_URL="redis://…" --app carbon-apiCarbon auto-enables TLS for Upstash hosts even when the URL starts
redis://.
3. Bootstrap the API
Section titled “3. Bootstrap the API”fly launch --config apps/api/fly.toml --copy-config --no-deploy --name carbon-api
fly secrets set --app carbon-api \ BETTER_AUTH_SECRET=$(openssl rand -hex 32) \ CARBON_METRICS_TOKEN=$(openssl rand -hex 16) \ ALLOWED_ORIGINS=https://dashboard.carbon.example
fly deploy --config apps/api/fly.toml --remote-only4. Bootstrap the dashboard
Section titled “4. Bootstrap the dashboard”fly launch --config apps/dashboard/fly.toml --copy-config --no-deploy --name carbon-dashboard
fly secrets set --app carbon-dashboard \ DATABASE_URL="$(fly secrets list --app carbon-api | grep DATABASE_URL | awk '{print $2}')" \ BETTER_AUTH_SECRET=... \ NEXT_PUBLIC_CARBON_API_URL=https://carbon-api.fly.dev
fly deploy --config apps/dashboard/fly.toml --remote-only.github/workflows/ci.yml redeploys both apps on push to main using
FLY_API_TOKEN. After the first bootstrap you never docker build
from a laptop again.
Health, drain, metrics
Section titled “Health, drain, metrics”/health— liveness (no dependency checks)/ready— readiness; 2s cached DB + Redis + storage probe- On
SIGTERMthe API fails/readyimmediately and keeps serving forCARBON_DRAIN_MS(default 5s). Set to0if your platform removes the instance from the pool before signalling. /metrics— Prometheus text; unauthenticated. SetCARBON_METRICS_TOKENand scrape withAuthorization: Bearer <token>when reachable from the internet.
See Prometheus for scrape queries and alert rules.
Scaling the API horizontally
Section titled “Scaling the API horizontally”fly scale count 3 --app carbon-api is safe out of the box — no sticky
sessions required. Rate limits, idempotency keys, the ingest queue, and
the CLI device-authorization flow (carbon auth login) all round-trip
through Redis, so a browser approve landing on instance A and the CLI’s
next poll hitting instance B pick up the same freshly minted secret.
Point every replica at the same REDIS_URL and you’re done.