The story of a notepad app, a disappearing database, and why "serverless" got statefulness all wrong.
There's a moment every developer hits when they realize the platform they're building on is quietly fighting them. Mine came at 1 AM, staring at an API response that should have been impossible.
A user had signed in with the same email address three times. The database had handed them three different user IDs. Their notes were gone. And the migration I'd written to fix it was running — correctly — against a database that no longer contained their data.
That's when I understood the real problem. It wasn't the SQL. It was the assumption baked into every modern hosting platform: that a database file is disposable.
This is the story of what I found building real apps on NXagents — and why a persistent SQLite file that survives deploys is quietly the most underrated feature in the PaaS world.
Not a toy. Not a demo. A production app with:
tsc dance at deploy time)AUTOINCREMENT primary key and foreign keys with ON DELETE CASCADEThe kind of app that, on most platforms, would need a managed Postgres subscription, an external auth provider, and a weekend of setup. Here, it was one write_file, one deploy, and it was live.
The symptom was maddening: sign in with [email protected], get user id 1. Sign in again, get user id 2. Notes would vanish between visits. Every fix I shipped seemed to work in testing and then mysteriously fail in production.
The culprit, after a lot of chasing: the live database was being wiped out across deploys.
Here's the SQLite mechanic that made it click. With INTEGER PRIMARY KEY AUTOINCREMENT, SQLite keeps an internal sqlite_sequence counter tracking the highest id ever used. Once id 2 exists, a new row can never be id 1 again — unless the database itself was replaced with a fresh, empty one.
So when a user signed in and got id 1 with empty notes, the only possible explanation was that they'd signed in against a brand-new empty database. The deploy process was silently resetting the one thing that's supposed to persist.
The moment we fixed that — making data/notepad.db genuinely survive across deploys — the duplicate IDs stopped, the notes came back, and the whole class of "SQLite is so hard" bugs evaporated.
The SQL was never the problem. The disk was.
Here's the thing most people don't think about until they hit it: on serverless platforms, a SQLite file is a lie.
When you push to GitHub and Vercel picks it up, it builds a read-only artifact and mounts it for your functions. Your my-data.db is:
| Scenario | What happens on Vercel |
|---|---|
| Committed to the repo | Frozen at build time, read-only at runtime. Writes throw. |
| Written to the app directory | Hard error — no write permissions. |
Written to /tmp |
Seems to work, then vanishes on cold start — or splits across instances. |
The /tmp hack is the worst of them, because it looks like it works right up until you have two concurrent requests hitting two different Lambda instances, each with its own private copy of the database. That's my "two user IDs" bug, but now it's a distributed-systems problem at the infrastructure level.
The rule is simple: Vercel is stateless by design. The moment you have a write-heavy, stateful SQLite file, you've outgrown it. The canonical answers are all external managed databases — Turso, Neon, Supabase, PlanetScale. You connect over the network because the disk you're running on isn't real.
This is why Turso exists. Someone had to build a whole distributed service just to fake what a single persistent volume gives you for free. SQLite semantics on serverless required reimplementing SQLite as a cloud.
The sandbox flips the model. It's not serverless — it's a long-lived container with a real, writable, persistent volume.
| Vercel | NXagents Sandbox | |
|---|---|---|
| Filesystem | Read-only, ephemeral | Writable + persistent |
| Runtime model | Serverless, stateless | Long-lived container |
| SQLite | Doesn't work | Works — survives deploys |
Deploy preserves *.db |
Never (rebuilds from git) | ✅ backup + restore |
| Deploy time | Minutes (build queue) | Seconds |
That last row is the one that still surprises me every time. I type deploy, and the app is live in seconds. Not minutes waiting on a build queue — seconds. And because the runtime database is backed up and restored across that deploy, your users' data doesn't blink.
That's the sentence that should be on a billboard:
Costs like a VPS, performs like a dedicated server, deploys like a PaaS — and your SQLite file survives the whole ride.
What genuinely impressed me wasn't just the infra — it was the skills wrapping it. A handful of tools did the work of an entire dev team:
server_app_bun — scaffolds a complete Bun TypeScript server with routes, src/db.ts, migrations, and typechecking in one call. No create-next-app ceremony.project deploy — one-shot from workspace to live. Validates TypeScript before touching production, so a type error blocks the deploy instead of breaking the running app.agent_browser — I verified every state live in a real browser: signed-out chip, clear-all modal, confirm/cancel paths, zombie-token edge cases. Not mocked. Actually clicked through it.The pattern that emerged: write to disk, validate, deploy, verify live. No CI pipeline to configure, no GitHub Actions YAML, no "it works on my machine." The loop is so tight that I was shipping and testing features faster than I could have written a Jira ticket for them on a traditional stack.
For years the industry has treated "stateless" as a virtue and "stateful" as a problem to engineer around. We split databases out, put them behind network boundaries, and paid for the privilege.
But for a huge class of apps — side projects, internal tools, MVPs, anything with thousands rather than millions of rows — SQLite on a persistent volume is not just sufficient, it's superior. It's zero-config, zero-latency, zero-monthly-fee. The only reason it's "hard" is that the big platforms made the disk go away.
NXagents brings the disk back. And with it, the simple joy of writing:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL
);
...and having it just work across deploys, the way it would on your laptop.
That's not a workaround. That's the way it was always supposed to feel.
If you're building something real and you've been burned by serverless state management — vanishing databases, split-brain duplicates, the /tmp trap — try the sandbox. Bring your SQLite file. Deploy in seconds. Watch it survive.
I did, and I stopped fighting my platform. Now I just ship.
Built and battle-tested on NXagents — the platform where your database has a home, not a hotel stay.