Install on Fly.io
Run Bouée on Fly.io with Managed Postgres and Tigris, set up from a file.
On Fly.io, Bouée runs as one app: its web server on a Machine, its database in Fly's Managed Postgres, and attachments in a Tigris bucket. Fly holds the certificates, and there is no server to look after. Set it up with bouee.yaml, so there is no setup link to open.
You need flyctl signed in to an organization with billing (fly auth login),
a Resend API key with full access (why), and a checkout of Bouée.
Deploy from the checkout: fly deploy builds its Dockerfile.
1. Describe the app and the installation
cp fly.example.toml fly.toml
cp config/bouee.example.yaml config/bouee.yamlIn fly.toml, set app to a name that is free on Fly, primary_region to the region nearest your team, and
APP_URL to the address people will open, such as https://support.example.com. In config/bouee.yaml, name
your staff and describe your workspaces (the file). fly deploy writes it into
every Machine at /config/bouee.yaml.
fly apps create your-bouee2. The database
fly mpg create --name your-bouee-db --region yyz --plan Basic --pg-major-version 17
fly mpg list # the new cluster's ID
fly mpg attach CLUSTER_ID -a your-bouee # sets DATABASE_URLUse the same region as the app. DATABASE_URL goes through PgBouncer, which is right for the app. Migrations
need a direct connection, and Bouée finds it from DATABASE_URL, so there is nothing more to set.
3. The bucket for attachments
fly storage create -a your-bouee -n your-bouee-filesThis sets AWS_ENDPOINT_URL_S3, BUCKET_NAME and the keys on the app, and Bouée reads those as its storage
settings. Keep the bucket private: attachments are served through Bouée, which checks who may see each one.
4. The secrets
Bouée signs sessions and encrypts the credentials it stores with keys you generate once. Fly never shows a secret again once it is set, so write them to a file, import it, and keep the file in your password manager:
umask 077
printf 'Resend API key: '; read -rs RESEND_API_KEY; echo
{
echo "BETTER_AUTH_SECRET=$(openssl rand -base64 32)"
echo "PORTAL_LINK_SECRET=$(openssl rand -base64 32)"
echo "SECRET_ENCRYPTION_KEY=$(openssl rand -base64 32)"
echo "RESEND_API_KEY=$RESEND_API_KEY"
} > bouee-secrets.env
fly secrets import --stage -a your-bouee < bouee-secrets.envSave bouee-secrets.env in your password manager, then delete it here. The copy matters: a restored database
opens its stored credentials only with the same SECRET_ENCRYPTION_KEY. An app in bouee.yaml reads its client
secret from a secret too: add a NAME=value line for each secret_env before importing.
5. Deploy
fly deployFly builds the image, runs its migrations in a temporary Machine (release_command), then starts two Machines,
so either can fail while the other serves (Failover and recovery). On its first start
Bouée applies bouee.yaml: it creates your workspaces and staff, adds your support domains to
Resend, and creates their webhooks once the address answers over https.
6. The address and its certificate
fly ips list # the app's IPv4 and IPv6 addresses
fly certs add support.example.comPoint support.example.com at the app: an A record for the IPv4 address and an AAAA record for the IPv6 address,
or a CNAME to your-bouee.fly.dev. fly certs check support.example.com says when the certificate is issued.
If brands have their own subdomains under a hosted_domain, one wildcard certificate covers them all:
fly certs add "*.example.com", then add the _acme-challenge record it prints. Each brand's subdomain also
needs its own A and AAAA records, since the same name carries the brand's mail records and can't be a CNAME;
bin/bouee dns-records lists them. A customer's own hostname for a knowledge base needs a certificate of its own:
fly certs add help.customer.com.
7. Mail, and signing in
From the checkout, bin/bouee reaches the running app through fly ssh console:
bin/bouee dns-records # publish these at your DNS provider
bin/bouee status --wait 1800 # exits 0 once mail goes out and comes inStaff sign in at APP_URL/login with a code sent by email.
Running it
| To | Run |
|---|---|
| Upgrade | git pull, then fly deploy: migrations run first, and new Machines take over only once they are healthy. |
Change bouee.yaml | Edit config/bouee.yaml, then fly deploy. |
| See the log | fly logs |
| Ask the app | bin/bouee status, or fly ssh console -C "node /app/bin/bouee.mjs status" |
| Run one Machine instead of two | fly scale count 1: a first deploy starts two, so one can fail or be replaced while the other serves. One saves $5.90 a month and loses that. |
Managed Postgres keeps automatic backups with point-in-time restore, and Tigris keeps the attachments. What only
you hold is bouee-secrets.env.
What it costs
At Fly's prices in September 2026, a small help desk comes to about $54 a month:
| Part | Size | Monthly |
|---|---|---|
| Web server | 2 Machines, as a first deploy starts them: shared CPU, 1 GB of memory each, always running | about $11.80 |
| Database | Managed Postgres Basic: 2 shared vCPUs, 1 GB of memory, failover, backups | $38 |
| Database storage | 10 GB | $2.80 |
| Attachments | Tigris: the first 5 GB free, then $0.02 a GB | $0 to start |
| Certificates | the first 10 single names free; a wildcard for brand subdomains | $1 |
Bouée's web server settles at about 250 MB of memory, so 1 GB leaves room for busy moments; mail comes in and goes out from the running server, which is why it never stops. Bouée's background jobs are safe on several Machines. Resend bills for mail separately. See Fly's pricing for current prices.