[GUIDE] 2/11 Migrations

This lab is intended to be a guide/reference. There are no deliverables.

The Jarvis migrator service lives at backend/migrator/ and uses Alembic to manage database schema migrations. Since all backend microservices share a single Postgres database, the migrator is a standalone service that handles migrations for every table.

If you look at past versions of Jarvis, you’ll notice that in the main.py of each service, we had this line of code:

@app.on_event("startup")
async def startup_event():
    """Create database tables on startup"""
    create_tables()

This would effectively attempt to create all tables on startup. However, if the schemas changed, this command would actually fail since it cannot determine what fields it needs to add.

Now, with alembic, we have a more robust migration system.

Project Structure

Path Description
alembic.ini Alembic configuration file
config.py Reads DATABASE_URL from .env.local
migrations/env.py Alembic environment; imports all SQLAlchemy models
migrations/versions/ Individual migration scripts (ordered by down_revision chain)
migrations/script.py.mako Template for generating new migration files

Prerequisites

Make sure your Docker Compose environment is running so the migrator can reach the database:

docker compose up --build

Create your .env.local

The migrator reads its database URL from backend/migrator/.env.local, which is gitignored. You need to create this file yourself, for our docker compose setup:

DATABASE_URL=postgresql://jarvis:password@localhost:5432/jarvis

Applying Existing Migrations

When you first clone the repo (or after pulling new migration files), you’ll need to apply all pending migrations to bring your database up to date:

# from backend/migrator
uv run alembic upgrade head

This applies every migration that hasn’t been run yet, in order. Alembic tracks which migrations have been applied via the alembic_version table in the database.

Creating a New Migration

When you change a SQLAlchemy model (e.g. add a column, create a new table), you need to generate a migration:

  1. Make sure the model is imported in migrations/env.py. Existing imports cover the User, Note, Chat, and Message models. If you add a new model, add its import there as well.

  2. Generate the migration:

uv run alembic revision --autogenerate -m "Short description of change"
  1. Review the generated file in migrations/versions/. Alembic auto-detects differences between your models and the current database state, but you should always verify the upgrade() and downgrade() functions look correct.

  2. Apply the migration:

uv run alembic upgrade head

Common Commands

Command Description
uv run alembic upgrade head Apply all pending migrations
uv run alembic downgrade -1 Roll back the last migration
uv run alembic downgrade <revision> Roll back to a specific revision
uv run alembic current Show the current migration revision
uv run alembic history Show the full migration history
uv run alembic revision --autogenerate -m "msg" Generate a new migration from model changes

Inspecting the Database

You can open a Postgres shell on the Docker Compose database to verify schema changes:

docker compose exec db psql -U jarvis -d jarvis

Then run \d to list all tables, or \d <table_name> to inspect a specific table’s columns.

Resetting the Database

If you need a clean slate (e.g. your local database is in a bad state), remove the Docker volume and re-apply migrations:

docker compose down
docker volume rm jarvis-monorepo_db_data
docker compose up --build
# Then from backend/migrator/:
uv run alembic upgrade head

How to use the Jarvis migrator service

2026-02-11