Database Migrations With Flask-Migrate

Introduction

db.create_all() creates tables once but cannot safely evolve schema when you add columns or rename fields. Flask-Migrate wraps Alembic to generate versioned migration scripts and apply them with flask db upgrade—the standard workflow for Flask apps in team and production environments.

Prerequisites

Install Flask-Migrate

bash
pip install Flask-Migrate

app/extensions.py:

python
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
 
db = SQLAlchemy()
migrate = Migrate()

app/__init__.py:

python
from app.extensions import db, migrate
 
def create_app(config_name="development"):
    app = Flask(__name__)
    ...
    db.init_app(app)
    migrate.init_app(app, db)
    ...
    return app

Code explanation:

  • migrate.init_app(app, db) registers Alembic CLI commands under flask db

Import models so Alembic sees metadata:

python
from app import models  # noqa: F401 — inside create_app after db.init_app

Initialize Migrations (Once Per Project)

bash
export FLASK_APP=app:create_app
flask db init

Creates:

text
migrations/
├── alembic.ini
├── env.py
├── README
└── versions/

Commit migrations/ to Git—see Git basics.

Generate a Migration

After changing models:

python
class User(db.Model):
    ...
    bio = db.Column(db.String(500))

Create revision:

bash
flask db migrate -m "Add bio to users"

Alembic writes migrations/versions/xxxx_add_bio_to_users.py with upgrade() and downgrade().

Review the script before applying—autogenerate can miss renames or data moves.

Apply Migrations

bash
flask db upgrade

Rollback one step:

bash
flask db downgrade

Show history:

bash
flask db history
flask db current

Code explanation:

  • upgrade applies pending revisions to match models
  • Production deploy runs flask db upgrade before or during app restart

Workflow Summary

text
Edit models → flask db migrate -m "message" → review script → flask db upgrade

Team flow:

  1. Developer A creates migration, commits
  2. Developer B pulls, runs flask db upgrade
  3. CI/CD runs upgrade on staging/production

MySQL-Specific Notes

Use utf8mb4 when creating database:

sql
CREATE DATABASE flask_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Alembic handles ALTER TABLE—align column types with MySQL data types.

Seed data is separate from migrations—use scripts or Flask CLI commands, not raw INSERT in Alembic unless intentional.

Warning

Never Edit Production DB by Hand

Manual ALTER drifts from migration history—always generate or hand-write a revision file.

Empty Database vs Existing Data

Adding nullable=False column without default fails on rows already present:

python
status = db.Column(db.String(20), nullable=False, server_default="active")

Or multi-step migration: add nullable column → backfill → set not null.

Testing Migrations

python
def test_upgrade(app):
    with app.app_context():
        from flask_migrate import upgrade
        upgrade()
        user = User(username="test", email="t@example.com")
        db.session.add(user)
        db.session.commit()

Use in-memory SQLite in TestingConfig for speed.

FAQ

flask db command not found?

Install Flask-Migrate; set FLASK_APP=app:create_app.

Target database is not up to date?

Run flask db upgrade or resolve merge heads with flask db merge.

Multiple migration heads?

Two branches created revisions—merge with flask db merge heads.

Autogenerate missed a change?

Alembic only detects model metadata—verify imports and write manual ops in revision.

SQLite limitation in dev?

Some MySQL types differ—test final migrations against MySQL before production.

Delete migrations folder?

Only before first deploy—otherwise you lose history; never on production.

Stamp without running?

flask db stamp head marks DB at revision without executing—use when aligning existing DB carefully.