Introduction
Managing migrations across multiple databases requires controlling which models live on which database, running migrations against the right targets, and handling schema differences.
Key Concepts
allow_migrate(): Router method controlling whether a migration runs on a given database.--databaseflag: CLI argument targeting a specific database for migrate/showmigrations.- Unmanaged models: Models with
managed = Falsefor external tables Django shouldn't alter.
Real World Context
Production setups commonly have a primary DB, read replicas, an analytics warehouse, and legacy databases. Each needs a different migration strategy.
Deep Dive
Router-Controlled Migrations
Database routers control which models migrate to which database by implementing allow_migrate(). Return True to allow, False to deny, or None to delegate:
pythonclass AnalyticsRouter: analytics_apps = {'analytics', 'reporting'} def allow_migrate(self, db, app_label, model_name=None, **hints): if app_label in self.analytics_apps: return db == 'analytics' if db == 'analytics': return False return None
The example above illustrates the pattern in practice. Now let's look at the next approach.
Running Migrations per Database
bashpython manage.py migrate # Default DB python manage.py migrate --database=analytics # Analytics DB python manage.py showmigrations --database=analytics
The example above illustrates the pattern in practice. Now let's look at the next approach.
Unmanaged Models
For tables owned by external systems or legacy applications, use managed = False to tell Django it should not create or alter the table:
pythonclass LegacyCustomer(models.Model): customer_id = models.IntegerField(primary_key=True) full_name = models.CharField(max_length=200) class Meta: managed = False db_table = 'customers' legacy = LegacyCustomer.objects.using('legacy').all()
Common Pitfalls
- Forgetting
--databaseflag —migratewithout it only touches the default database. allow_migratereturning None unexpectedly — None delegates to the next router. Return True/False for definitive answers.- Cross-database foreign keys — Foreign keys across databases don't work. Use ID fields with manual lookups.
Best Practices
- One router per concern — Separate routers for replicas, analytics, and legacy databases.
- Use
managed = Falsefor external tables — Never let Django manage tables owned by other systems. - Automate in CI/CD — Include all
--databasecommands in deployment scripts.
Summary
- Control migration targets with
allow_migrate()in routers. - Use
--database=<alias>to target specific databases. - Set
managed = Falsefor external/legacy tables. - Automate multi-database migrations in your deployment pipeline.
Code Examples
class AnalyticsRouter:
analytics_apps = {'analytics', 'reporting'}
def allow_migrate(self, db, app_label, **hints):
if app_label in self.analytics_apps:
return db == 'analytics'
if db == 'analytics':
return False
return None
# Run: python manage.py migrate --database=analytics