Back to KB
Difficulty
Intermediate
Read Time
5 min

Building a SQLite Migration Runner in Python

By Codcompass Team··5 min read

Building a SQLite Migration Runner in Python

A small migration runner is a practical tool every team with embedded databases eventually needs. This tutorial shows how to version schema changes, apply them safely, and keep a simple audit trail using plain SQL and Python.

Why this pattern works

SQLite exposes a built-in version slot called PRAGMA user_version, which you can use to track the current schema version. The core idea is simple: read the version on startup, run every numbered migration you have not applied yet, and bump the version only after each migration succeeds.

This approach is useful because it keeps the database self-describing without needing a heavy framework. It also makes local development, CI, and deployed databases converge on the same schema state in a predictable way.

Project structure

A clean layout keeps migrations easy to review and hard to break. Use one file per migration, number them in order, and store them in a dedicated folder.

project/
  app.py
  migrations/
    001_initial.sql
    002_add_created_at.sql
    003_add_indexes.sql

Enter fullscreen mode Exit fullscreen mode

Each migration should do one logical change, because that makes review and rollback simpler. Once a migration is shipped, do not edit it; add a new one instead.

The migration rules

Follow four rules from the start. First, migrations are numbered consecutively. Second, each migration runs inside a transaction. Third, user_version changes only after the schema change succeeds. Fourth, never rewrite a shipped migration.

These rules prevent the classic “works on my machine” problem where different machines have different schema history. They also make failures safer because SQLite can roll back transactional schema changes cleanly.

Write your SQL files

Start with a migration that creates your initial table. Keep the SQL explicit and readable so future changes stay obvious.

 migrations/001_initial.sql
BEGIN;

CREATE TABLE IF NOT EXISTS notes (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  body TEXT NOT NULL
);

PRAGMA user_version = 1;

COMMIT;

Enter fullscreen mode Exit fullscreen mode

Then add a second migration for a common

🎉 Mid-Year Sale — Unlock Full Article

Base plan from just $4.99/mo or $49/yr

Sign in to read the full article and unlock all 635+ tutorials.

Sign In / Register — Start Free Trial

7-day free trial · Cancel anytime · 30-day money-back