There's a recurring argument in indie hacker circles: real apps use Postgres. SQLite is for tutorials.
This is wrong. And if you're a solo builder shipping in 2026, believing it might be costing you weeks of setup time you'll never get back.
What SQLite Actually Gives You
SQLite isn't a toy database. It's a battle-tested library used by every iPhone, every Android device, every Firefox install, and (quietly) many production SaaS products. At sub-10,000 daily-active-user scale—which describes 99% of solo projects in their first two years—SQLite:
- Needs zero server process to manage
- Persists as a single file you can
cpfor backups - Handles thousands of writes per second on commodity hardware
- Has zero connection overhead (no TCP round-trip to a separate host)
FastAPI + SQLite: The Minimum Viable Stack
Here's the full setup that powers several of the tools listed on builds.anethoth.com:
pip install fastapi uvicorn sqlmodel
# models.py
from sqlmodel import SQLModel, Field, create_engine, Session
engine = create_engine("sqlite:///app.db")
class Item(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
value: float
SQLModel.metadata.create_all(engine)
# main.py
from fastapi import FastAPI
from sqlmodel import select
app = FastAPI()
@app.get("/items")
def list_items():
with Session(engine) as s:
return s.exec(select(Item)).all()
That's it. No docker-compose Postgres service. No migration runner to bootstrap. No connection pool to tune. You run uvicorn main:app and you're live.
When You Actually Need Postgres
Be honest with yourself here. You need Postgres when:
- Multiple processes write concurrently from separate machines (SQLite's file-lock model doesn't scale across hosts)
- You need full-text search with complex ranking (though SQLite FTS5 covers most cases)
- Your dataset exceeds ~1TB (SQLite is technically unlimited but tooling gets harder)
For the first 18 months of any indie project, none of these apply. Ship with SQLite. Migrate when metrics force you to.
The Practical Migration Path
Here's the reassuring part: SQLModel and SQLAlchemy abstract the dialect. Swapping SQLite for Postgres is one line:
# Before
engine = create_engine("sqlite:///app.db")
# After (with DATABASE_URL env var)
engine = create_engine(os.environ["DATABASE_URL"])
If you've structured your data layer with SQLModel or SQLAlchemy, the migration is a weekend project, not a rewrite.
The Real Cost of Over-Engineering Early
The opportunity cost isn't just setup time. It's the mental overhead. Every hour you spend configuring pg_hba.conf and SSL certs and connection pooling is an hour not spent talking to users, improving the product, or writing the marketing copy that actually moves the needle.
Start simple. Ship fast. Reach the scale where Postgres makes sense—that's a good problem to have.
Explore the tools at builds.anethoth.com — each one started with a SQLite file and a weekend.