Cloudflare Pages + Railway + Neon: deploying a self-hosted app for $0
No software background. Built a license key manager called KeyMint with Cursor, deployed it across three free-tier services, and hit five errors I didn't see coming. Here's what they were.
The stack
Frontend on Cloudflare Pages, API on Railway, database on Neon. All free. React 19 + Vite on the frontend, Express 5 + TypeScript on the backend, standard PostgreSQL through Drizzle ORM.
Error 1: Railway free tier limit
Went to create a new Railway project for KeyMint. Railway said no — already at the limit with two existing projects.
Fix: don't create a new project. Add KeyMint as a new service inside the existing project. Railway lets you run multiple services under one project. I had just assumed each app needed its own. Two minutes to figure out, zero code changes.
Error 2: Supabase was full too
Free tier on Supabase is two projects. Both slots were taken.
I switched to Neon instead of deleting something. Same free-tier PostgreSQL, same pg driver, different connection string. Swapped the DATABASE_URL in Railway's env vars and moved on. No code changes.
If you're on free tiers, don't get attached to one database host. They're interchangeable.
Error 3: pg in the wrong package.json
API server deployed and immediately crashed on boot. Generic module resolution error, not helpful.
The actual problem: pg was a dependency of the shared lib/db package, not of @keymint/api-server directly. esbuild bundled the api-server, marked pg as external because it wasn't in that package's own dependencies, and at runtime Node couldn't find it.
Fix — add pg directly to the api-server's package.json:
{
"dependencies": {
"pg": "^8.11.0"
}
}
Monorepo gotcha. esbuild doesn't crawl the whole workspace to figure out what you need at runtime. If a dep lives in a sibling package, it gets left out of the bundle.
Error 4: Wrong Cloudflare product
Cloudflare has Pages and Workers. They look similar in the dashboard. They are not the same thing.
I ended up in Workers first. Workers wants wrangler deploy, a config file, edge functions. That's not what I needed — I just wanted to host a Vite build.
Pages is the right product. Connect GitHub, set build command, set output directory, done.
Settings that worked for a pnpm monorepo:
- Build command:
pnpm install && pnpm --filter @keymint/web build - Output directory:
artifacts/keymint/dist - Environment variable:
NODE_VERSION=20
That last one matters. Cloudflare Pages defaults to an older Node version. Without setting it to 20, the build fails on syntax it doesn't recognize. Spent 15 minutes on that.
Error 5: wrangler deploy kept failing
Related to error 4 — while I was in the Workers section, the dashboard was telling me to run wrangler deploy. It kept failing because I had no wrangler.toml and wasn't writing a Worker.
Once I found the Pages section and used the GUI deploy flow instead, it worked.
This sounds obvious. It wasn't obvious when I was in it.
End result
KeyMint is live at keymint.pages.dev. Frontend on Cloudflare Pages, API on Railway, database on Neon. Free across the board.
Source code on GitHub: github.com/ibrh96-prog/keymint
Packaged it as a boilerplate if you want to skip the setup: ibrh96.gumroad.com/l/nvkfg — $49, one-time, full source.
Built with Cursor. 0 sales so far — testing whether dev.to drives any.
Top comments (0)