Generate Django Database Migrations
Commands
Generate migrations automatically based on model changes:
sentry django makemigrations
For a specific app:
sentry django makemigrations <app_name>
Generate an empty migration (for data migrations or custom work):
sentry django makemigrations <app_name> --empty
After Generating
- If you added a new model, ensure it's imported in the app's
__init__.py - Review the generated migration for correctness
- Run
sentry django sqlmigrate <app_name> <migration_name>to verify the SQL - Apply the migration locally with
sentry django migrate <app_name>— Sentry's migration framework runs its safety checks on apply, so this catches unsafe ops (missingis_post_deployment, unsafe column changes, etc.) before CI does.
When editing a generated migration (e.g. swapping DeleteModel for SafeDeleteModel), leave the auto-generated is_post_deployment comment block in place. It documents a non-obvious flag with concrete guidance for future migration authors — useful context, not fluff. Only remove a comment if it's stale or contradicts the code.
Guidelines
Adding Columns
- Use
db_default=<value>instead ofdefault=<value>for columns with defaults - Nullable columns: use
null=True - Not null columns: must have
db_defaultset
Adding Indexes
For large tables, set is_post_deployment = True on the migration as index creation may exceed the 5s timeout.
Deleting Columns
- Make column nullable (
null=True) if not already - Remove all code references
- Replace
RemoveFieldwithSafeRemoveField(..., deletion_action=DeletionAction.MOVE_TO_PENDING) - Deploy, then create second migration with
SafeRemoveField(..., deletion_action=DeletionAction.DELETE)
Removing a Model (and eventually its table)
Two-phase process — the historical_silo_assignments entry must be added in phase 1.
Phase 1 — Remove the model class (MOVE_TO_PENDING)
- Remove all code references
- Replace
DeleteModelwithSafeDeleteModel(..., deletion_action=DeletionAction.MOVE_TO_PENDING) - Add the table to
historical_silo_assignmentsinsrc/sentry/db/router.py(orgetsentry/db/router.pyfor getsentry models). Pick the silo the model used — usuallySiloMode.CELL. - Deploy
Phase 2 — Drop the table (DELETE)
After phase 1 has deployed, create a second migration with SafeDeleteModel(..., deletion_action=DeletionAction.DELETE). Leave the historical entry in place — the table-drop migration relies on it to resolve the silo.
Renaming Columns/Tables
Don't rename in Postgres. Use db_column or Meta.db_table to keep the old name.
Resolving Merge Conflicts
If migrations_lockfile.txt conflicts:
bin/update-migration <migration_name>
This renames your migration, updates dependencies, and fixes the lockfile.