Bouée
Get started

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.yaml

In 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-bouee

2. 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_URL

Use 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-files

This 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.env

Save 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 deploy

Fly 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.com

Point 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 in

Staff sign in at APP_URL/login with a code sent by email.

Running it

ToRun
Upgradegit pull, then fly deploy: migrations run first, and new Machines take over only once they are healthy.
Change bouee.yamlEdit config/bouee.yaml, then fly deploy.
See the logfly logs
Ask the appbin/bouee status, or fly ssh console -C "node /app/bin/bouee.mjs status"
Run one Machine instead of twofly 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:

PartSizeMonthly
Web server2 Machines, as a first deploy starts them: shared CPU, 1 GB of memory each, always runningabout $11.80
DatabaseManaged Postgres Basic: 2 shared vCPUs, 1 GB of memory, failover, backups$38
Database storage10 GB$2.80
AttachmentsTigris: the first 5 GB free, then $0.02 a GB$0 to start
Certificatesthe 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.

On this page