All articles
SQLitePythonArchitecture

SQLite as an Embedded Database: Patterns for Zero-Dependency Tools

PyForge Team 7 min read2026-10-14

The default answer to "where do I store state" has become a managed database with a monthly invoice. For self-hosted developer tools that is overkill: SQLite is transactional, battle-tested and already installed everywhere Python runs.

One file is a feature

A database you can copy with cp, back up with a cron job and inspect with a single binary changes how you operate. Enable WAL mode and you get concurrent readers with a writer — more than enough for a single-node tool.

python
import sqlite3

conn = sqlite3.connect("tool.db")
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")

Migrations without a framework

You do not need Alembic for a single-file database. A user_version pragma and an ordered list of migration functions gives you safe, forward-only schema upgrades in thirty lines.

python
MIGRATIONS = [
    "CREATE TABLE events (id TEXT PRIMARY KEY, ts INTEGER)",
    "ALTER TABLE events ADD COLUMN payload TEXT",
]

def migrate(conn):
    v = conn.execute("PRAGMA user_version").fetchone()[0]
    for i, sql in enumerate(MIGRATIONS[v:], start=v):
        conn.execute(sql)
        conn.execute(f"PRAGMA user_version={i + 1}")

When not to use SQLite

Be honest about the ceiling: many writers across many machines is Postgres territory, not SQLite. But for CLIs, agents and single-node services — the vast majority of developer tools — the embedded model removes an entire category of operational failure.

That is why the whole PyForge catalog is built this way: your tool should run on a five-dollar VPS or a laptop with equal confidence.