Daeseon Yoo
Back to project
·Tech retro·2 min

Killing Fly for $0, an 18-char NXDOMAIN, and going Supabase-direct on the web seam

Tore down the Fly server to guarantee no charges, deployed the web to Vercel, lost an hour to a Supabase project ref that was 18 chars instead of 20 (NXDOMAIN), then retired the custom server entirely — the web now reads and writes Supabase Postgres directly via RLS, with an atomic claim RPC for the ticket lock.

Three things happened, in order.

1. Fly out, for a hard $0

The master server (Hono + node:sqlite) was on Fly. Fly bills for the volume (~$0.15/GB-mo) even idle, and the ask was an absolute zero — no money out, ever. The only way to guarantee that is to own no billable resources, so I destroyed the app and its volume outright (fly apps destroy talkak-master). It was throwaway scaffold anyway: SQLite-on-a-disk only made sense because Fly gave a disk.

2. Web on Vercel — and an 18-character ghost

Built apps/web locally (Supabase keys baked from a gitignored .env.local) and deployed the prebuilt dist to Vercel as a static site — sidestepping all the pnpm-monorepo build friction. Login screen came up. Signup threw Failed to fetch.

I'd earlier concluded "my sandbox can't reach supabase.co." Wrong. The user's own browser showed DNS_PROBE_FINISHED_NXDOMAIN; their terminal nslookup of the project host returned NXDOMAIN while curl ifconfig.me worked fine. The project ref I'd been using — jmqjkhkhgxpkplyyvw — was 18 characters. Supabase refs are 20. The real one was jmqjkhkhgxpkplyyvwsl; I'd dropped the trailing sl. So every Supabase request anyone made resolved to a domain that does not exist. Fixed the constant, rebuilt, redeployed — login worked, verified by the user signing in with their real email.

Lesson logged: NXDOMAIN + a working ifconfig.me means the hostname is wrong, full stop — not blocked, not a sandbox. Check an external identifier's shape (a Supabase ref is 20 chars) before blaming the network.

3. Retiring the server — Supabase-direct

With Fly gone and Supabase already doing auth, keeping a second backend made no sense. So the web now talks to Supabase Postgres directly via supabase-js, with RLS as the security boundary:

Schema + RLS + the RPC + a demo seed live in apps/web/supabase/schema.sql, run once in the Supabase SQL editor. No custom server to host, nothing on Fly, all within free tiers. The "절제" (restraint) call: the desktop app is the product; the cloud seam should lean on what's already there, not grow a second backend.