| name | db-migrations |
| description | Alembic database migration workflows for FastAPI/SQLModel applications. Use when creating migrations, running schema changes, handling failed migrations, switching database engines, or resetting local state. Triggers on alembic, migration, schema change, database, upgrade, downgrade. |
Database Migrations
Alembic database migration workflows for the Vibe4Vets backend application.
Core Setup
Working directory: backend/
Migration tool: Alembic with SQLModel
Database: PostgreSQL (production), SQLite (optional local)
Primary Workflows
Creating a Migration
cd backend
alembic revision --autogenerate -m "add phone column to resource"
alembic upgrade head
Running Migrations
cd backend
alembic upgrade head
alembic upgrade abc123
alembic downgrade -1
alembic downgrade abc123
alembic current
alembic history
Common Migration Operations
Add Column
def upgrade() -> None:
op.add_column('resource', sa.Column('phone', sa.String(), nullable=True))
def downgrade() -> None:
op.drop_column('resource', 'phone')
Add Column with Default
def upgrade() -> None:
op.add_column('resource', sa.Column('trust_score', sa.Float(), nullable=False, server_default='0.0'))
op.alter_column('resource', 'trust_score', server_default=None)
def downgrade() -> None:
op.drop_column('resource', 'trust_score')
Rename Column
def upgrade() -> None:
op.alter_column('resource', 'name', new_column_name='title')
def downgrade() -> None:
op.alter_column('resource', 'title', new_column_name='name')
Add Index
def upgrade() -> None:
op.create_index('ix_resource_category', 'resource', ['category'])
def downgrade() -> None:
op.drop_index('ix_resource_category', table_name='resource')
Add Foreign Key
def upgrade() -> None:
op.add_column('resource', sa.Column('source_id', sa.Integer(), nullable=True))
op.create_foreign_key(
'fk_resource_source',
'resource', 'source',
['source_id'], ['id']
)
def downgrade() -> None:
op.drop_constraint('fk_resource_source', 'resource', type_='foreignkey')
op.drop_column('resource', 'source_id')
Create Table
def upgrade() -> None:
op.create_table(
'source',
sa.Column('id', sa.Integer(), primary_key=True),
sa.Column('name', sa.String(), nullable=False),
sa.Column('url', sa.String(), nullable=True),
sa.Column('tier', sa.Integer(), nullable=False, default=4),
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
)
op.create_index('ix_source_name', 'source', ['name'], unique=True)
def downgrade() -> None:
op.drop_index('ix_source_name', table_name='source')
op.drop_table('source')
Data Migrations
For complex data transformations, create manual migrations:
from alembic import op
import sqlalchemy as sa
def upgrade() -> None:
conn = op.get_bind()
conn.execute(sa.text("""
UPDATE resource
SET trust_score = source.reliability_score * 0.9
FROM source
WHERE resource.source_id = source.id
AND resource.trust_score IS NULL
"""))
def downgrade() -> None:
conn = op.get_bind()
conn.execute(sa.text("UPDATE resource SET trust_score = NULL"))
Troubleshooting
Alembic Doesn't Detect Changes
from app.models.resource import Resource
from app.models.organization import Organization
from app.models.source import Source
target_metadata = SQLModel.metadata
Migration Targeting Error
alembic upgrade head
alembic revision --autogenerate -m "description"
Failed Migration
alembic current
alembic upgrade head
alembic downgrade -1
alembic upgrade head
alembic stamp head
SQLite Limitations
SQLite doesn't support all ALTER operations. If you hit constraints:
export DATABASE_URL="postgresql://user:pass@localhost:5432/vibe4vets"
alembic upgrade head
Reset Local Database
dropdb vibe4vets
createdb vibe4vets
alembic upgrade head
alembic downgrade base
alembic upgrade head
Production Workflow
Pre-Deploy Checklist
Deploy Steps
pg_dump $DATABASE_URL > backup_$(date +%Y%m%d_%H%M%S).sql
alembic upgrade head
alembic current
Rollback Steps
alembic downgrade -1
alembic current
Zero-Downtime Migration Strategy
For large tables or production systems:
def upgrade():
op.add_column('resource', sa.Column('new_field', sa.String(), nullable=True))
def upgrade():
conn = op.get_bind()
conn.execute(sa.text("""
UPDATE resource SET new_field = old_field
WHERE new_field IS NULL
LIMIT 10000
"""))
def upgrade():
op.alter_column('resource', 'new_field', nullable=False)
def upgrade():
op.drop_column('resource', 'old_field')
Best Practices
DO
- Always review autogenerated migrations
- Test migrations locally before deploying
- Use descriptive migration names
- One logical change per migration
- Include downgrade logic
- Backup before production migrations
DON'T
- Edit already-applied migrations in shared environments
- Skip reviewing autogenerated code
- Run migrations without backup
- Make migrations irreversible without good reason
- Mix schema and data migrations in one file