Failover and recovery
What keeps Bouée answering when a Machine, a deploy or the database fails, and what a region outage needs.
Install on Fly.io sets up an installation that keeps answering through the failures that happen most often, with nothing to do at the time: two Machines, a database that fails over by itself, and deploys that switch to new Machines only once they are healthy. Bouée keeps nothing that matters in a Machine's memory. Sessions, the outgoing mail queue and attachments live in the database and the bucket, so either Machine can answer any request, and losing one loses nothing.
What it survives
| When | What happens |
|---|---|
| A Machine fails, or its host does | The other Machine keeps serving. Fly's proxy stops sending it requests once its health check fails, and restarts a Machine whose process exits. |
| You deploy | Migrations run first, in a temporary Machine. New Machines then start beside the running ones and take over only once /api/health answers, so a release that does not start never serves. A Machine being replaced has 30 seconds to finish its requests and hand its mail to Resend. |
| The database's primary fails | Managed Postgres promotes its standby. Requests made during the switch fail, and Bouée replaces each dropped connection on the next query; the log says [db] a database connection dropped. |
| Resend is down | Replies and notifications wait in Bouée's queue and go out once Resend answers again. |
| Bouée cannot be reached | Resend retries incoming mail with backoff for about ten hours, and a delivery it gave up on can be replayed from its dashboard (Resend: retries and replays). |
Background jobs (retries, expiry, auto-close, backups) run on every Machine and are safe to: each claims its work in
the database, or does work that is the same done twice. bouee.yaml is applied under a database lock, one Machine
at a time.
Deploys that stay up
Two versions run side by side for a minute during every deploy: the new one against the migrated database, and the old one still serving until the new one is healthy. So a release never removes what the release before it uses. It adds what it needs, and a later release removes what nothing uses any more. Bouée's own tests hold every migration to that. Keep it in mind for a fork of your own.
A release that fails its health check never takes traffic. To go back after one that started but misbehaves,
deploy the version before it: git checkout <previous tag>, then fly deploy.
A whole region
Managed Postgres keeps its standby in its own region, so losing the region takes the database with it. What brings
it back is Bouée's own database backups: encrypted, every hour, into the Tigris
bucket, which keeps them in several regions. bin/bouee backups lists them. Recovering takes about half an hour,
and loses at most the work since the last backup; DATABASE_BACKUP_EVERY_MINUTES=15 narrows that to 15 minutes.
-
Create a database in a region that is up:
fly mpg create --name your-bouee-db-2 --region ewr --plan Basic --pg-major-version 17 -
Restore the newest backup into it, from a temporary Machine in that region. Paste the new cluster's pooled connection string, from its Connection tab, when asked:
printf 'New DATABASE_URL: '; read -rs NEW_DATABASE_URL; echo fly console --region ewr -e DATABASE_URL="$NEW_DATABASE_URL" -C "/app/bin/restore --latest"It needs the installation's
SECRET_ENCRYPTION_KEY, which the Machine has from the app's secrets: the backups are encrypted with it. -
Point the app at the new database, and start its Machines there:
fly secrets set --stage DATABASE_URL="$NEW_DATABASE_URL" fly scale count 2 --region ewr fly scale count 0 --region yyzSet
primary_regionto the new region infly.tomlfor the nextfly deploy. -
Check it:
bin/bouee status. DNS needs no change, since the app's addresses are the same in every region, and the bucket and the secrets belong to the app, not a region.
Rehearse it once while nothing is wrong: restore into a new cluster with fly console, check it with fly mpg connect, then destroy it. /app/bin/restore --list shows every backup, and /app/bin/restore <key> restores an
older one.
Watching it
- An uptime check on
https://support.example.com/api/health, which answers 200 only while the database does. bin/bouee status --jsonfrom a scheduled job: it exits 1 when mail stops going out or coming in.fly logs, and the Machines' metrics on Fly's dashboard.