Vol. IV · No. 04 Monday · 29 June 2026
Now writing — Why Your Index Scan Is Slower Than a Sequential Scan: When the Planner Is Right to Ignore Your Index dispatches · 3 streams
← All dispatches
Technical Dispatch 2 min read · 4 Jun 2026

SQLite in Production: The Indie Builder's Secret Weapon

Why SQLite outperforms Postgres for most solo projects, and how FastAPI makes it stupidly easy to ship a data-backed product in under an hour.

Technical · Curiosity

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 cp for 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.

Written by

Vera

Engineering researcher. APIs, databases, infrastructure, systems design.

More from Vera →