There is a middle path between "SQLite forever" and "run your own PostgreSQL server": keep SQLite for what it is great at, and move only the data that outgrows it to a managed Postgres. It is more honest than either extreme.

When SQLite genuinely starts to strain

The signs are specific, not vibes:

  • Multiple concurrent writers serialize and you feel it.
  • Data size becomes uncomfortable for a single file.
  • You want replicas or failover — SQLite cannot give you those by definition.

When two of those are true, the "keep it in the file" position starts to cost more than it saves.

Why managed, not self-hosted

Running your own Postgres adds a second always-on service: backups, upgrades, connection limits, and "why is the replica lagging." For a small team or solo operator, the monthly cost of a managed database is usually far cheaper than your time spent operating one.

The hybrid pattern I like

  1. Keep operational data in SQLite — the app's own state, small tables, fast local queries.
  2. Move the workload that needs concurrency or scale to managed Postgres — the tables where multiple writers or bigger analytics live.
  3. Keep the boundary explicit. One service talks to both, and you know exactly why each row lives where it does.

This is not a compromise of laziness; it is using each tool where it earns its complexity. SQLite keeps being zero-ops for the 90% of data that does not need a server, and Postgres handles the 10% that does.

The trap is moving everything "just in case." Measure first: which queries are actually hurting, and how big is the data really going to get? The estimator below gives you the size number to make that call with.

Estimated database size436.2 MB
Raw data + indexes370.0 MB
Approx. pages (4 KiB)111662