Every indie builder hits the same moment: someone looks at your tech stack, sees SQLite, and says "you'll need to migrate to Postgres eventually." And you nod, feeling vaguely embarrassed, and make a mental note to do it someday.
Stop. That mental note is costing you time you don't have.
When SQLite is the right call
SQLite is the right database for your indie SaaS when three conditions hold:
- You have fewer than 10,000 concurrent users
- Your read/write ratio is at least 10:1
- You are one or two people
That describes most indie SaaS products at every stage that matters — early, mid, and often late. Discourse ran on SQLite until they hit millions of daily users. Basecamp didn't use a distributed database when 37signals was five people. The embarrassment is manufactured.
What SQLite actually gives you: zero network latency between app and database, trivial backups (copy one file), no connection pool configuration, no separate database server to maintain, and query performance that will not be your bottleneck. Your bottleneck will be the business logic you haven't written yet.
The three things you actually need
WAL mode. Run PRAGMA journal_mode=WAL once at startup. This switches SQLite from exclusive locking to write-ahead logging, which allows concurrent readers and a single writer without blocking each other. Without this, your app serializes every read behind every write. With it, reads and writes coexist, and you stop caring about read concurrency.
Litestream for backups. Litestream is a background process that replicates your SQLite file to S3 (or R2, or any object store) continuously, using the WAL. Point-in-time recovery. Zero code changes. Run it as a sidecar. Your backup strategy is now solved.
A write serializer. SQLite allows one concurrent writer. Don't fight this — serialize writes explicitly. In Python, use a threading.Lock or a queue. In Node, use a promise queue. The serializer is five lines of code and eliminates any "database is locked" errors. Most SQLite horror stories trace back to applications that didn't do this.
The one thing that will actually bite you
Schema migrations. SQLite's ALTER TABLE is limited — you can't drop columns in older versions, can't change column types, and can't add constraints to existing tables. You work around this by creating a new table, copying data, and renaming. Use a migration tool (Alembic, Flyway, or a simple numbered SQL file pattern) from day one. This is the real operational discipline SQLite requires.
Everything else — concurrent reads, backup recovery, query performance — is solved by the three items above.
The graduation myth
Migrating from SQLite to Postgres is not a rite of passage. It's an operational cost you pay when you've confirmed, through real load, that SQLite is your actual bottleneck — not a theoretical one. Most indie products never reach that load before other constraints (distribution, retention, pricing) determine their fate.
Ship the product. Validate the business. Migrate if and when you have to. Until then, the one-file database that runs in your process, needs no configuration, and backs up with a copy command is a feature, not technical debt.
Anethoth builds tools for indie founders. If you're an indie builder or early-stage SaaS founder, Builds tracks products like yours — from first revenue to profitability.