Deploy the Account portal to Fly.io
LIVE (2026-07-16) at https://account.findmydata.io (andhttps://findmydata-account.fly.dev) — the Account portal runs on Fly from the
public GHCR image in production + Clerk (idp) mode. This doc records how it was
deployed and how to redeploy.
Provisioned (Fly account bob@findmydata.io)
- Fly app
findmydata-account, oneshared-cpu-1x/512mbmachine iniad(min_machines_running = 0→ scales to zero when idle). - Persistent volume
fmd_account_data(1 GB,iad) for the shared vendor DB. - IPs: shared IPv4
66.241.125.49, dedicated IPv6. - TLS cert Issued for
account.findmydata.io(fly certs add …); the CloudflareaccountCNAME →findmydata-account.fly.dev(DNS-only) is in the Cloudflare runbook. - Config:
deploy/fly/account.fly.toml— runsbun run --cwd packages/server account,FMD_ACCOUNT_ENV=production,FMD_ACCOUNT_AUTH_MODE=idp, the Clerk public issuer + publishable key, the Stripe price ids, and the/datamount.
account_started … mode=production authMode=idp.
Redeploy
ghcr.io/clarkdickens/findmydata:latest (via the release workflow)
then redeploying picks up the new image. The Clerk instance is a development
instance (shows a dev banner + strict usage limits) — promote it to a Clerk
production instance for a clean prod domain when going GA.
Enable Stripe billing (optional, after deploy)
Billing is OFF until the two Stripe secrets are set (the price ids are already in the config). Set them as Fly secrets (never in the repo):https://findmydata-account.fly.dev/account/v1/billing/webhook, subscribe to
checkout.session.completed + customer.subscription.deleted, and use its
signing secret as STRIPE_WH.
Plain support mirror — ENABLED
The machine-user API key is set as a Fly secret (FMD_PLAIN_API_KEY_REF=PLAIN_KEY),
so a support case opened in the portal opens a Plain thread. Only case metadata
crosses (subject, severity, opener email/name, portal case id) — never customer
content; failures never block case creation. The key is scoped to least privilege
(customer:create, customer:edit, thread:create) and the endpoint defaults to
https://core-api.uk.plain.com/graphql/v1 (FMD_PLAIN_API_URL overrides). To
rotate: fly secrets set PLAIN_KEY=plainApiKey_… -a findmydata-account.
Custom domain — DONE
account.findmydata.io is live: fly certs add account.findmydata.io (cert
Issued) + a Cloudflare account CNAME → findmydata-account.fly.dev (DNS-only),
recorded in the Cloudflare runbook.
Clerk production instance — DNS done, flip pending Clerk verification
A Clerk production instance forfindmydata.io exists, and all 5 Clerk CNAMEs
(clerk, accounts, clkmail, clk._domainkey, clk2._domainkey) are in
Cloudflare (DNS-only) and resolve correctly. deploy/fly/account.fly.toml
already carries the production values (FMD_CLERK_ISSUER=https://clerk.findmydata.io,
FMD_CLERK_PUBLISHABLE_KEY=pk_live_…, FMD_CLERK_AUTHORIZED_PARTIES=https://account.findmydata.io).
However, Clerk’s own DNS verification is asynchronous and was still 0/5 at deploy
time, so clerk.findmydata.io’s Frontend API returns 403 (not yet provisioned).
Deploying the production issuer while it 403s would break sign-in. So the running
app is pinned to the dev Clerk instance via a Fly secrets override (secrets
take precedence over [env]):
curl https://clerk.findmydata.io/.well-known/jwks.json returns 200:
account.fly.toml take effect.
Verify sign-in from https://account.findmydata.io (the Clerk dev-key banner should
be gone). Rollback: re-run the fly secrets set block above.
Enable Stripe billing (optional — still OFF)
Billing stays OFF until the two Stripe secrets are set (the price ids are already in the config):https://account.findmydata.io/account/v1/billing/webhook, subscribe to
checkout.session.completed + customer.subscription.deleted, and use its signing
secret as STRIPE_WH.