Database migrations
Schema evolution for the main application database (the one DATABASE_URL
points at) is owned by Alembic. PostgreSQL
is the default backend; SQLite is used in tests and local development.
How it is wired
Section titled “How it is wired”alembic.iniholds a placeholder URL; the real URL is injected at runtime fromDATABASE_URL.migrations/env.pyimports every model module so autogenerate sees all tables. SQLite getsrender_as_batch=Truebecause it has no nativeALTER COLUMN.migrations/versions/contains the migration scripts, starting from a baseline that captures the whole schema.apps/api/db_init.pyrunsalembic upgrade headat startup. Migration errors are fatal and never masked bycreate_all().apps/api/scripts/migrate.pyis the one-shot deployment entrypoint. Docker Compose runs it as themigrateservice and requires it to finish before the API and worker replicas start.
Startup behaviour: YUPCHA_DB_INIT
Section titled “Startup behaviour: YUPCHA_DB_INIT”| Value | Effect |
|---|---|
alembic (default) |
alembic upgrade head: creates a fresh schema and applies pending migrations |
create_all |
Base.metadata.create_all(): dev/test fallback, never alters existing tables, refused outside dev/test/local |
skip |
Do nothing; the caller manages the schema |
Common commands
Section titled “Common commands”uv run alembic upgrade head # apply all migrationsuv run alembic current # current revisionuv run alembic historyuv run alembic downgrade -1 # roll back one stepuv run alembic check # CI: non-zero exit if models drifted from migrationsSet DATABASE_URL to target a specific database.
Writing a new migration
Section titled “Writing a new migration”-
Edit the SQLAlchemy models.
-
Autogenerate against a database that is already at
head, so the diff is only your change. A throwaway SQLite file works:Terminal window export DATABASE_URL="sqlite:///./data/_scratch.db"uv run alembic upgrade headuv run alembic revision --autogenerate -m "describe the change" -
Review the generated script. Autogenerate is not perfect: check column types, server defaults, indexes, RLS policies and data migrations, and make sure batch operations look sane for SQLite.
-
Confirm there is no leftover drift with
alembic upgrade headfollowed byalembic check. -
Commit the new file under
migrations/versions/.
Datastores outside Alembic
Section titled “Datastores outside Alembic”Alembic manages only tables in the main database’s SQLAlchemy metadata. Two control-plane stores are intentionally separate and single-node today:
data/workspaces.db: workspaces, settings, members and the active-workspace pointer, created with plain SQL in the workspace manager.- Per-workspace collection ledgers under
data/workspaces/<slug>/.
Moving these into PostgreSQL is the first item on the hosted-operation roadmap.