[{"data":1,"prerenderedAt":92},["ShallowReactive",2],{"qa-\u002Ffastapi\u002Fdatabase\u002Fmigrations":3},{"page":4,"siblings":83,"blog":74},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":73,"related":74,"seo":75,"seoDescription":76,"stem":77,"subtopic":78,"topic":79,"topicSlug":80,"updated":81,"__hash__":82},"qa\u002Ffastapi\u002Fdatabase\u002Fmigrations.md","Migrations",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","FastAPI","fastapi",{},true,3,"\u002Ffastapi\u002Fdatabase\u002Fmigrations",[23,28,32,36,40,44,49,53,57,61,65,69],{"id":24,"difficulty":25,"q":26,"a":27},"what-is-alembic","easy","What is Alembic and why do you use it with FastAPI instead of `create_all`?","**Alembic** is SQLAlchemy's official database migration tool. It tracks schema\nchanges as versioned migration scripts and applies them incrementally.\n\n`Base.metadata.create_all()` only **creates missing tables** — it cannot alter\nexisting columns, add indexes, rename tables, or roll back changes. Alembic can\ndo all of this and keeps an audit trail.\n\n```\nalembic upgrade head     # apply all pending migrations\nalembic downgrade -1     # roll back the last migration\nalembic history          # show migration history\n```\n\nRule of thumb: use `create_all` only in local dev and test databases; use\nAlembic for every schema change in staging and production.\n",{"id":29,"difficulty":25,"q":30,"a":31},"alembic-init","How do you initialise Alembic in a FastAPI project?","```bash\npip install alembic\nalembic init alembic    # creates alembic\u002F directory and alembic.ini\n```\n\nDirectory structure created:\n```\nalembic\u002F\n├── env.py          # migration environment (connect to DB)\n├── script.py.mako  # template for new migration files\n└── versions\u002F       # generated migration scripts live here\nalembic.ini         # config (sqlalchemy.url, etc.)\n```\n\nEdit `alembic.ini` to set the DB URL, or set it dynamically in `env.py`:\n```python\n# alembic\u002Fenv.py\nfrom app.config import settings\nconfig.set_main_option(\"sqlalchemy.url\", settings.database_url)\n```\n\nRule of thumb: set the DB URL from environment variables in `env.py` rather\nthan hard-coding it in `alembic.ini` — `alembic.ini` is committed to git.\n",{"id":33,"difficulty":14,"q":34,"a":35},"alembic-env-py","What changes do you make to `alembic\u002Fenv.py` to support autogenerate?","Point `target_metadata` to your `Base.metadata` so Alembic can compare the\ncurrent schema against your ORM models:\n\n```python\n# alembic\u002Fenv.py\nfrom app.db.base import Base          # import Base\nfrom app.db.models import user, order  # ensure all models are imported!\n\ntarget_metadata = Base.metadata\n\ndef run_migrations_online():\n    connectable = engine_from_config(\n        config.get_section(config.config_ini_section),\n        prefix=\"sqlalchemy.\",\n    )\n    with connectable.connect() as connection:\n        context.configure(connection=connection, target_metadata=target_metadata)\n        with context.begin_transaction():\n            context.run_migrations()\n```\n\nThe model imports are critical — Alembic only knows about tables whose models\nare imported at migration time.\n\nRule of thumb: create a `db\u002Fbase_all.py` that imports every ORM model and\nimport it in `env.py` — it's easy to forget a new model.\n",{"id":37,"difficulty":25,"q":38,"a":39},"autogenerate-migration","How do you auto-generate a migration script with Alembic?","```bash\nalembic revision --autogenerate -m \"add users table\"\n```\n\nAlembic compares `target_metadata` (your ORM models) to the current DB schema\nand generates a script with the diff:\n\n```python\n# alembic\u002Fversions\u002Fabc123_add_users_table.py\ndef upgrade():\n    op.create_table(\n        \"users\",\n        sa.Column(\"id\", sa.Integer(), nullable=False),\n        sa.Column(\"name\", sa.String(100), nullable=False),\n        sa.PrimaryKeyConstraint(\"id\"),\n    )\n\ndef downgrade():\n    op.drop_table(\"users\")\n```\n\nAlways **review the generated script** before applying — Alembic can't detect\nrenames (it sees a drop + create) and may miss custom indexes or constraints.\n\nRule of thumb: treat autogenerate as a starting point, not a final answer —\nalways read the diff and edit it if needed before committing.\n",{"id":41,"difficulty":25,"q":42,"a":43},"upgrade-downgrade","How do you apply and roll back Alembic migrations?","```bash\n# Apply all pending migrations\nalembic upgrade head\n\n# Apply exactly N migrations forward\nalembic upgrade +2\n\n# Roll back the most recent migration\nalembic downgrade -1\n\n# Roll back to a specific revision\nalembic downgrade abc123\n\n# Roll back everything\nalembic downgrade base\n```\n\nAlembic tracks the current revision in the `alembic_version` table in the DB.\n\nRule of thumb: always write a proper `downgrade()` function — even if you never\nplan to roll back in production, it's invaluable in development and CI.\n",{"id":45,"difficulty":46,"q":47,"a":48},"alembic-async","hard","How do you configure Alembic to run async migrations (asyncpg)?","Use `AsyncEngine` and `run_async_migrations` in `env.py`:\n\n```python\n# alembic\u002Fenv.py\nimport asyncio\nfrom sqlalchemy.ext.asyncio import create_async_engine\n\ndef do_run_migrations(connection):\n    context.configure(connection=connection, target_metadata=target_metadata)\n    with context.begin_transaction():\n        context.run_migrations()\n\nasync def run_async_migrations():\n    engine = create_async_engine(settings.database_url)\n    async with engine.connect() as conn:\n        await conn.run_sync(do_run_migrations)\n    await engine.dispose()\n\ndef run_migrations_online():\n    asyncio.run(run_async_migrations())\n```\n\nThe key is `conn.run_sync(do_run_migrations)` — Alembic's migration API is\nsynchronous; you bridge it with `run_sync`.\n\nRule of thumb: async migration setup is boilerplate — copy the pattern from the\nofficial Alembic docs rather than hand-rolling it.\n",{"id":50,"difficulty":46,"q":51,"a":52},"migration-in-production","What is the best practice for running Alembic migrations in production?","Never run migrations from inside the FastAPI `lifespan` function in production.\nInstead:\n\n**Option A — Pre-deployment step** (recommended):\n```bash\n# In CI\u002FCD pipeline, before rolling out new pods\nalembic upgrade head\n```\n\n**Option B — Init container** (Kubernetes):\n```yaml\ninitContainers:\n  - name: migrate\n    image: myapp:latest\n    command: [\"alembic\", \"upgrade\", \"head\"]\n```\n\nReasons to avoid in-app migrations:\n- Multiple pods start simultaneously → race condition running migrations twice.\n- A failed migration crashes the app before it can serve requests.\n- Slow migrations block the startup health check.\n\nRule of thumb: decouple migrations from app startup — run them as a separate\nstep with exactly one executor, with a rollback plan if the migration fails.\n",{"id":54,"difficulty":46,"q":55,"a":56},"data-migration","How do you write a data migration in Alembic (not just schema changes)?","Use `op.execute()` for SQL or `op.get_bind()` to access the connection for\nORM-style operations:\n\n```python\nfrom alembic import op\nimport sqlalchemy as sa\n\ndef upgrade():\n    # Schema change\n    op.add_column(\"users\", sa.Column(\"full_name\", sa.String(200)))\n\n    # Data migration — populate full_name from existing columns\n    conn = op.get_bind()\n    conn.execute(\n        sa.text(\"UPDATE users SET full_name = first_name || ' ' || last_name\")\n    )\n\n    # Make it non-nullable now that data is populated\n    op.alter_column(\"users\", \"full_name\", nullable=False)\n\ndef downgrade():\n    op.drop_column(\"users\", \"full_name\")\n```\n\nRule of thumb: do schema changes, then data migration, then add constraints —\nnever add a NOT NULL constraint before populating the column.\n",{"id":58,"difficulty":46,"q":59,"a":60},"multiple-heads","What is a merge migration in Alembic and when do you need one?","A **multiple heads** situation occurs when two developers create migrations from\nthe same base revision — Alembic has two \"latest\" revisions and doesn't know\nwhich to apply next.\n\n```bash\nalembic heads   # shows: abc123  xyz789  (two heads)\nalembic merge heads -m \"merge feature_a and feature_b\"\n```\n\nThis creates a new migration that has both revisions as parents, resolving the\ndivergence. The merge migration itself is usually empty.\n\nRule of thumb: include the `alembic_version` table (or the `versions\u002F` directory)\nin code review — catch diverged heads early rather than after merging to main.\n",{"id":62,"difficulty":25,"q":63,"a":64},"alembic-version-table","What is the `alembic_version` table and what does it contain?","Alembic creates a single-row table `alembic_version` in the target database to\ntrack which revision is currently applied:\n\n```sql\nSELECT * FROM alembic_version;\n-- version_num\n-- abc123def456   (hash of the latest applied migration)\n```\n\nWhen you run `alembic upgrade head`, it reads this table, finds all unapplied\nrevisions in order, runs their `upgrade()` functions, and updates `version_num`.\n\nRule of thumb: never edit `alembic_version` manually — if it gets out of sync,\nuse `alembic stamp \u003Crevision>` to set it without running migration code.\n",{"id":66,"difficulty":14,"q":67,"a":68},"alembic-stamp","What does `alembic stamp` do and when would you use it?","`alembic stamp \u003Crevision>` sets the `alembic_version` table to the given revision\n**without running any migration code**. Use it when:\n\n1. You're bootstrapping a new DB that already has the schema (e.g., cloned from\n   prod) and you want to mark it as current without running all migrations.\n2. You manually applied a schema change and want Alembic to know it's done.\n\n```bash\nalembic stamp head      # mark DB as fully migrated (no code runs)\nalembic stamp abc123    # mark a specific revision as current\n```\n\nRule of thumb: use `alembic stamp head` when creating a fresh DB from `create_all`\nto keep Alembic in sync; never use it to skip failed migrations in production.\n",{"id":70,"difficulty":14,"q":71,"a":72},"migration-testing","How do you test Alembic migrations in a CI pipeline?","Run upgrade + downgrade in CI against a test database:\n\n```bash\n# In CI\nalembic upgrade head      # apply all migrations\nalembic downgrade base    # roll all the way back\nalembic upgrade head      # re-apply (round-trip test)\n```\n\nFor individual migration testing in pytest:\n```python\n@pytest.fixture\ndef migrated_db(postgres_url):\n    engine = create_engine(postgres_url)\n    with engine.begin() as conn:\n        alembic_cfg = Config(\"alembic.ini\")\n        alembic_cfg.set_main_option(\"sqlalchemy.url\", postgres_url)\n        command.upgrade(alembic_cfg, \"head\")\n    yield engine\n    with engine.begin() as conn:\n        command.downgrade(alembic_cfg, \"base\")\n```\n\nRule of thumb: test up + down in CI on every PR — a migration that upgrades\nsuccessfully but can't downgrade will strand you if a release needs to roll back.\n",12,null,{"description":11},"FastAPI Alembic migration interview questions — setup, autogenerate, upgrade, downgrade, async migrations and production best practices.","fastapi\u002Fdatabase\u002Fmigrations","Alembic Migrations","Database Integration","database","2026-06-20","2iCSPW0I4hfqjDOfBqCpp72k6A21Eg3YDczynF-U0Uw",[84,88,91],{"subtopic":85,"path":86,"order":87},"SQLAlchemy (Sync)","\u002Ffastapi\u002Fdatabase\u002Fsqlalchemy",1,{"subtopic":89,"path":90,"order":12},"Async Database","\u002Ffastapi\u002Fdatabase\u002Fasync-db",{"subtopic":78,"path":21,"order":20},1782244113438]