Claude Skills Guide

Database migrations are one of the most critical yet often frustrating aspects of application development. When working with SQLAlchemy and Alembic, getting migrations right can mean the difference between a smooth deployment and hours of emergency fixes. This guide shows you how to use Claude Code to streamline your migration workflow, from initial model design to production deployments.

Understanding the Migration Challenge

Every growing application eventually needs schema changes. Adding new tables, modifying columns, creating indexes—these operations seem simple until you have multiple developers working on different features across various environments. Migration conflicts emerge, rollback strategies get forgotten, and suddenly your database state becomes a source of anxiety rather than reliability.

Claude Code can help by automating much of the tedious work around migrations while ensuring you maintain control over critical database decisions. The key is understanding how to effectively communicate with Claude about your schema requirements and migration needs.

Setting Up Your SQLAlchemy Environment

Before diving into migrations, ensure your project is properly configured. Your SQLAlchemy models should be organized in a way that makes them easy to inspect and modify. A typical project structure includes your models in a dedicated module, with clear relationships defined.

# models/base.py
from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, Integer, DateTime
from datetime import datetime

Base = declarative_base()

class TimestampMixin:
    created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)

This pattern gives you automatic timestamp tracking across all models. When you generate migrations, Alembic will recognize these mixins and handle them appropriately.

Generating Your First Migration

The most common workflow starts with model changes. Suppose you’re adding a new feature requiring a UserProfile table. Rather than manually writing the migration, describe your model to Claude Code and ask for migration generation.

When working with Claude, provide context about your existing models and the relationship you want to establish. For example: “Generate a migration to add a UserProfile table with a one-to-one relationship to the existing User model. Include fields for bio, avatar_url, and preferred_language.”

Claude can then help you create the model definition and generate the corresponding Alembic migration:

# models/user_profile.py
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from models.base import Base, TimestampMixin

class UserProfile(Base, TimestampMixin):
    __tablename__ = 'user_profiles'
    
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'), unique=True, nullable=False)
    bio = Column(String(500), nullable=True)
    avatar_url = Column(String(255), nullable=True)
    preferred_language = Column(String(10), default='en')
    
    user = relationship("User", back_populates="profile")

The corresponding Alembic migration would look like this:

# migrations/versions/001_add_user_profiles.py
"""add user profiles table

Revision ID: abc123
Revises: 
Create Date: 2026-03-14 10:00:00.000000

"""
from alembic import op
import sqlalchemy as sa

revision = 'abc123'
down_revision = None
branch_labels = None
depends_on = None

def upgrade():
    op.create_table(
        'user_profiles',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('user_id', sa.Integer(), nullable=False),
        sa.Column('bio', sa.String(length=500), nullable=True),
        sa.Column('avatar_url', sa.String(length=255), nullable=True),
        sa.Column('preferred_language', sa.String(length=10), nullable=True),
        sa.Column('created_at', sa.DateTime(), nullable=False),
        sa.Column('updated_at', sa.DateTime(), nullable=False),
        sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
        sa.UniqueConstraint('user_id')
    )

def downgrade():
    op.drop_table('user_profiles')

Reviewing and Refining Migrations

One of Claude’s greatest strengths is helping you review migrations before execution. Common issues include missing indexes on foreign keys, forgetting to handle existing data when adding non-nullable columns, and not accounting for production data volumes.

When reviewing a migration with Claude, ask specific questions: “Does this migration handle existing rows when adding the required field?” or “What indexes are being created for the foreign key relationships?” These questions help identify potential problems before they reach production.

For columns that need default values with existing data, ensure your migration includes an upgrade step that backfills the default:

def upgrade():
    # Add column with a default
    op.add_column('users', sa.Column('status', sa.String(20), server_default='active'))
    # Then remove the server_default if you don't want it permanent
    op.alter_column('users', 'status', server_default=None)

Handling Complex Schema Changes

Some migrations require more thought. Renaming tables, splitting columns, or performing data migrations need careful planning. Claude can help you break these down into safer, reversible steps.

Consider a data migration where you’re normalizing a denormalized field. Instead of trying to do everything in one migration, break it into phases: first add the new tables and columns, then populate them with data, then switch your application to use the new structure, and finally remove the old columns. This approach provides rollback points if something goes wrong.

def upgrade():
    # Phase 1: Add new normalized structure
    op.create_table('user_settings',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('user_id', sa.Integer(), nullable=False),
        sa.Column('key', sa.String(50), nullable=False),
        sa.Column('value', sa.String(255), nullable=False),
    )
    op.create_index('ix_user_settings_user_id', 'user_settings', ['user_id'])
    
    # Note: Data migration happens separately or via batch operation

def downgrade():
    op.drop_table('user_settings')

Best Practices for Migration Workflows

Following these practices will help you maintain database integrity while keeping your team productive.

Always generate migrations from model changes rather than manually writing them. This ensures your migration files accurately reflect your code models and reduces the chance of drift between your ORM definitions and actual database schema.

Test migrations in a staging environment that mirrors production data volume and characteristics. A migration that works perfectly with empty tables may timeout or lock tables when run against millions of rows.

Keep migrations small and focused. Large, complex migrations are harder to review, more likely to fail, and more difficult to roll back. If a change is substantial, consider breaking it into multiple sequential migrations.

Document non-obvious decisions in your migration comments. Why did you choose a specific index? What data transformations are being performed? Future developers—including yourself—will thank you.

Finally, include rollback strategies in your migration reviews. Every migration should have a clear path backward, even if you never need to use it.

Conclusion

Database migrations don’t have to be a source of stress. By using Claude Code to generate, review, and refine your Alembic migrations, you can move faster while maintaining confidence in your database schema changes. The key is treating migrations as first-class citizens in your development workflow, giving them the same attention and review as your application code.

Start by integrating Claude into your migration workflow on a small project, then expand to production systems once you’re comfortable with the collaboration pattern. Your future self—deployed at 3 AM during a critical release—will appreciate the investment.

Built by theluckystrike — More at zovo.one