# Managoat on Fly.io — https://fly.io/docs/reference/configuration/ # # This file is for somebody who wants an instance of their own on Fly. It is # not how the maintainer's instance runs (that is Kubernetes, ADR 0032), so treat it # the way you would treat docker-compose.yml: a starting point that comes up, # not a description of production. # # fly launch --no-deploy --copy-config # keeps this file, names the app # fly mpg create # managed Postgres # fly mpg attach # sets DATABASE_URL # fly ips allocate-v4 # dedicated IPv4, for the broker's TCP port # fly secrets set SECRET_KEY_BASE=... MASTER_SECRETS_KEY=... SPRITES_TOKEN=... \ # BROKER_PROXY_URL=https://.fly.dev:14322 # fly deploy --ha=false # one machine, see [http_service] # # Generate the two keys before you start: # # openssl rand -base64 48 | tr -d '\n' # SECRET_KEY_BASE # openssl rand 32 | base64 | tr '+/' '-_' | tr -d '=\n' # MASTER_SECRETS_KEY # # The full list of what you can set afterwards is docs/configuration.md, and # docs/guides/operate/hosts.md ("Fly.io") is the guide this file belongs to. # `fly launch` replaces this with the name you give it. Nothing else in this # file names the app, and Managoat reads FLY_APP_NAME for its own base URL. app = "fountain" primary_region = "iad" [build] # A pinned release, deliberately not `latest`: `latest` moves on every merge # to main, across breaking minors, with migrations applied at boot and # downgrade unsupported. Upgrade by editing this tag. Releases publish # vX.Y.Z (immutable) and vX.Y (newest patch in the line). # # To run your own build of a fork instead of the published image, delete the # `image` line and let Fly build the Dockerfile in this checkout. The build # compiles the umbrella from source, so # prefer the image unless you have changes to run. image = "ghcr.io/managoat/managoat:v0.39.2" # Migrations run at boot, inside the app's own start sequence, and the # endpoint does not listen until they finish. There is deliberately no # `[deploy] release_command`: a release machine would run the same migrations # a few seconds earlier, against the same database, for no gain. [deploy] # Rolling, on the one machine this app runs: Fly stops the machine, updates # it and starts it again, so the old and the new release never run at the # same time. Managoat runs as one node, and that is the property it needs: # `canary` and `bluegreen` bring a new machine up beside the old one, and # two machines are two schedulers holding the same sandboxes. A deploy # therefore has a short gap in which the app answers nothing; at boot the # rehydrator starts a server again for every conversation that had one. strategy = "rolling" [env] PHX_SERVER = "true" # `fly mpg attach` sets DATABASE_URL to a *.flympg.net host, which resolves # to a private IPv6 (6PN) address only. Without this the app looks the host # up as IPv4, finds nothing, and exits at boot with :nxdomain. DATABASE_IPV6 = "true" # Fly's proxy sends to whatever `internal_port` says. The image defaults to # 4000, so this has to be explicit rather than inherited. PORT = "8080" # Fly terminates TLS at its edge and forwards over the private network, so # the app sees the proxy rather than the caller. Left unset, the built-in # defaults are cluster CIDRs that match nothing here, every per-IP # rate-limit bucket collapses into one, and every audit row records the # proxy's address. fdaa::/16 is the 6PN range Fly gives an organization; # the IPv4 range covers a proxy that reaches the machine over the host. TRUSTED_PROXIES = "fdaa::/16,172.16.0.0/12" # No SMTP_HOST, so mail is off and a first deploy needs nothing external. # Accounts self-verify at registration (ADR 0011). For mail set SMTP_HOST, # SMTP_USERNAME, SMTP_PASSWORD and EMAIL_FROM with `fly secrets set`. # "auto": open so the first account can be created, and the first verified # account becomes admin; registration then closes on its own (ADR 0011). # Register before you hand the URL to anybody. "true" keeps it open for # everyone, for good. REGISTRATION_ENABLED = "auto" # Read only by images up to v0.39.2; newer images always promote the first # verified account and ignore it. Drop it once the pin is past. FIRST_USER_ADMIN = "true" # There is no OTLP collector in a default Fly setup, and without this the # exporter retries per span and floods the logs. OTEL_TRACES_EXPORTER = "none" # The egress credential broker (ADR 0019) listens here; [[services]] below # publishes it. BROKER_PROXY_URL, the address a sandbox dials, is set with # `fly secrets set` because it names the app. The app does not boot # without it. BROKER_LISTEN_PORT = "14322" # PUBLIC_URL is deliberately absent. `.fly.dev` is known the moment you # pick the name, but this file ships with a name you are about to change, # and a stale base URL is silently wrong rather than broken: it builds the # links in verification emails and every sandbox reads it as # MANAGOAT_BASE_URL. config/runtime.exs derives https://$FLY_APP_NAME.fly.dev # instead. Set PUBLIC_URL here when you add a custom domain, and it wins. # Fly's managed Postgres serves TLS, which the app expects by default. The # older unmanaged `fly pg create` app does not serve TLS at all, and against # one of those the app cannot connect until you uncomment this: # DATABASE_SSL = "false" [http_service] internal_port = 8080 force_https = true # Run exactly one machine. Managoat runs as a single node, and a second # machine is a split brain: two schedulers racing over the same sandboxes, # and streams that break for a viewer on the other machine. Fly starts two # machines on the first deploy of an app with a service unless you pass # `fly deploy --ha=false`; if you already have two, `fly scale count 1`. # # These two lines are the whole reason this file exists. Fly's defaults stop # an idle machine and start it again on a request, which is right for a web # app and wrong here: the sandbox reaper runs inside this process, so a # parked machine is an instance that quietly stops reaping. auto_stop_machines = "off" auto_start_machines = false min_machines_running = 1 [[http_service.checks]] # Fly gates traffic on this, which is a readiness question. /health is # static and would pass with an unreachable database. path = "/health/ready" interval = "15s" timeout = "5s" # Boot runs migrations before the endpoint listens. Do not count failures # against the machine while that is still happening. grace_period = "60s" # The egress broker, as raw TCP: a forward proxy is reached with CONNECT, # which the HTTP handler does not carry. The `tls` handler terminates TLS at # Fly's edge and passes the plaintext proxy protocol through. A TCP service # on IPv4 needs a dedicated address (`fly ips allocate-v4`). [[services]] internal_port = 14322 protocol = "tcp" # The same one always-on machine as [http_service]. auto_stop_machines = "off" auto_start_machines = false min_machines_running = 1 [[services.ports]] port = 14322 handlers = ["tls"] [[vm]] size = "shared-cpu-2x" memory = "2gb"