Skip to main content

Deploy the Account portal to Fly.io

LIVE (2026-07-16) at https://account.findmydata.io (and https://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, one shared-cpu-1x/512mb machine in iad (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 Cloudflare account CNAME → findmydata-account.fly.dev (DNS-only) is in the Cloudflare runbook.
  • Config: deploy/fly/account.fly.toml — runs bun 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 /data mount.
Verified live: portal 200 on both hostnames (TLS ok); Clerk sign-in loads; the dev-only register route returns 501 in idp mode; boot log shows account_started … mode=production authMode=idp.

Redeploy

Pushing a new 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):
Then in Stripe → Developers → Webhooks, add an endpoint at 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 for findmydata.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]):
To flip to production once the Clerk dashboard shows the domain Verified and curl https://clerk.findmydata.io/.well-known/jwks.json returns 200:
That drops the override so the production values in 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):
Then in Stripe → Developers → Webhooks add an endpoint at https://account.findmydata.io/account/v1/billing/webhook, subscribe to checkout.session.completed + customer.subscription.deleted, and use its signing secret as STRIPE_WH.