Skip to content

feat: fly.io provisioning IaC for server, postgres, redis and scylla - #308

Open
jeferson-sb wants to merge 1 commit into
masterfrom
feat/fly-io-provisioning
Open

feat: fly.io provisioning IaC for server, postgres, redis and scylla#308
jeferson-sb wants to merge 1 commit into
masterfrom
feat/fly-io-provisioning

Conversation

@jeferson-sb

Copy link
Copy Markdown
Owner

Summary

Expresses the docker-compose.yml topology as Fly.io IaC under infra/fly/, as a POC — nothing is deployed, and the root fly.toml (generated in 2022, before Redis/Postgres/Scylla existed) is deleted in favour of it.

Four apps on one 6PN private network, rather than one app with process groups, since only the chat tier scales on connection count and each store needs its own image, volume and restart semantics:

App Config Notes
chatme infra/fly/server/fly.toml 3+ machines, public HTTPS/wss, auto_stop_machines = "off"
chatme-postgres infra/fly/postgres/fly.toml stock postgres:17-alpine + volume
chatme-redis infra/fly/redis/ redis.conf with appendonly, maxmemory-policy noeviction
chatme-scylla infra/fly/scylla/ 3 nodes, volume each, entrypoint wrapper

Three decisions worth reading the ADR (docs/adr/2026-08-18-fly-io-provisioning.md) for:

  • Fly Proxy replaces nginx — it already terminates TLS and upgrades websockets, so nginx.conf has no deployed counterpart. Concurrency is type = "connections" (soft 200 / hard 250), because a Socket.IO client holds one long-lived socket and request counts would describe nothing. No sticky sessions needed: websocket-only + Redis adapter.

  • Scylla on an IPv6-only network is the non-obvious part. There is no stable per-node hostname, so infra/fly/scylla/entrypoint.sh derives seeds from DNS at boot before handing over to the image's entrypoint:

    seeds=$(getent ahostsv6 chatme-scylla.internal | awk '{print $1}' | sort -u | paste -sd, -)
    exec /docker-entrypoint.py --seeds "$seeds" \
      --listen-address "$FLY_PRIVATE_IP" --broadcast-address "$FLY_PRIVATE_IP" \
      --broadcast-rpc-address "$FLY_PRIVATE_IP" --api-address 127.0.0.1 \
      --endpoint-snitch GossipingPropertyFileSnitch ...

    It also sets enable_ipv6_dns_lookup: true and writes dc=$FLY_REGION, which is why the server's SCYLLA_LOCAL_DATACENTER is the region string — a mismatch makes the driver see no local nodes. provision.sh scales nodes one at a time so two never bootstrap an empty ring in parallel.

  • Migrations run from the workstation over fly proxy, not on release: the server image is built with pnpm install --prod, which prunes @better-auth/cli and the migrate scripts' dev deps.

infra/fly/provision.sh is the entrypoint — idempotent, step-selectable (./infra/fly/provision.sh server), and ordered stores → migrate → server, since the server exits at startup without a reachable DATABASE_URL. Secrets (BETTER_AUTH_SECRET, POSTGRES_PASSWORD, DATABASE_URL) are generated/wired by the script; REDIS_URL and SCYLLA_CONTACT_POINTS are plain .internal config. No store publishes a public port (ports = []).

Known gaps (documented in infra/fly/README.md)

Single-node Postgres and Redis with no backups or replication; no requirepass on Redis (6PN isolation only); RF=3 SimpleStrategy assumes one region; TCP-only health checks until the server grows a /health route; machine sizes are starting points, not measurements.

Verification

TOML parses and shellcheck is clean. fly config validate needs an authenticated token, so the configs have not been validated against Fly's schema, and no fly deploy was attempted.

Link to Devin session: https://app.devin.ai/sessions/7aa1398f2dac44648da2cf63cda1a0f7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant