In a multi-tenant Django application using django-tenants, migrations are handled differently than in a standard Django application. The system distinguishes between:
- Shared apps: Apps that exist in the public schema only (e.g.,
tenants,users) - Tenant apps: Apps that exist in each tenant schema (e.g.,
employees,titles,teams)
If you make changes to shared apps (apps in SHARED_APPS in settings.py), run:
python manage.py migrate_schemas --sharedShared apps include:
django_tenantstenantsusersdjango.contrib.contenttypesdjango.contrib.sessionsdjango.contrib.messagesdjango.contrib.staticfilesdjango.contrib.admindjango.contrib.authrest_frameworkrest_framework_simplejwtcorsheadersdrf_spectaculardjango_extensionstenant_users.permissionstenant_users.tenants
Example:
# After modifying tenants/models.py
python manage.py makemigrations tenants
python manage.py migrate_schemas --sharedIf you make changes to tenant-specific apps (apps in TENANT_APPS in settings.py), run:
python manage.py migrate_schemasTenant apps include:
django.contrib.admindjango.contrib.authdjango.contrib.contenttypesdjango.contrib.messagesdjango.contrib.staticfilestenant_users.permissionsemployeestitlesteams
Example:
# After modifying employees/models.py
python manage.py makemigrations employees
python manage.py migrate_schemasWhat happens:
- Migrations are applied to all existing tenant schemas
- New tenants will automatically have these migrations applied when created
To create migration files, use the standard Django command:
# Create migrations for a specific app
python manage.py makemigrations <app_name>
# Create migrations for all apps
python manage.py makemigrations
# Dry run (check what would be created)
python manage.py makemigrations --dry-runExamples:
python manage.py makemigrations employees
python manage.py makemigrations tenants
python manage.py makemigrations titles- Make model changes in your app's
models.py - Create migration file:
python manage.py makemigrations <app_name>
- Apply migrations:
- For shared apps:
python manage.py migrate_schemas --shared - For tenant apps:
python manage.py migrate_schemas
- For shared apps:
- Test the changes in your development environment
# 1. Modify model
# Edit employees/models.py
# 2. Create migration
python manage.py makemigrations employees
# 3. Review migration file
# Check employees/migrations/XXXX_*.py
# 4. Apply to all tenant schemas
python manage.py migrate_schemas
# 5. Test
python manage.py test employees# 1. Create migrations locally
python manage.py makemigrations <app_name>
# 2. Commit migration files
git add <app>/migrations/
git commit -m "Add migration for <app>"
git push
# 3. Deploy code
# 4. Apply migrations
python manage.py migrate_schemas --shared # If shared app
python manage.py migrate_schemas # If tenant appWhen using Docker, run migrations inside the container:
# Shared apps
docker-compose exec web python manage.py migrate_schemas --shared
# Tenant apps
docker-compose exec web python manage.py migrate_schemas
# Or using uv
docker-compose exec web uv run python manage.py migrate_schemas --shared
docker-compose exec web uv run python manage.py migrate_schemasBefore applying migrations, review the generated migration file:
python manage.py makemigrations <app_name>
# Review the generated file in <app>/migrations/Always test migrations in your development environment before deploying:
# Test on local database
python manage.py migrate_schemas --shared
python manage.py migrate_schemasBefore applying migrations in production, backup your database:
# PostgreSQL backup
pg_dump -U hrm_user hrm_db > backup_$(date +%Y%m%d_%H%M%S).sqlDjango migrations are transactional by default. If a migration fails, it will rollback automatically.
For data migrations in tenant apps, ensure you're in the correct tenant context:
# employees/migrations/0002_migrate_data.py
from django.db import migrations
from django_tenants.utils import schema_context
def migrate_employee_data(apps, schema_editor):
Employee = apps.get_model('employees', 'Employee')
# Get all tenant schemas
from tenants.models import Client
for tenant in Client.objects.all():
with schema_context(tenant.schema_name):
# Perform data migration
Employee.objects.filter(...).update(...)
class Migration(migrations.Migration):
dependencies = [
('employees', '0001_initial'),
]
operations = [
migrations.RunPython(migrate_employee_data),
]Problem: Migration applied to public schema instead of tenant schemas
Solution:
- For tenant apps, use
migrate_schemas(without--shared) - For shared apps, use
migrate_schemas --shared
Problem: Migration works on some tenants but fails on others
Solution:
- Check tenant-specific data that might cause issues
- Use
--schemaflag to test on specific tenant:python manage.py migrate_schemas --schema=tenant_name
Problem: Multiple developers created migrations with same number
Solution:
- Rename migration files to resolve conflicts
- Update dependencies in migration files
- Test thoroughly before committing
Problem: Migration fails due to missing dependencies
Solution:
- Check
dependencieslist in migration file - Ensure all required migrations are applied
- Use
--fakeflag carefully if needed (not recommended)
# Check shared apps
python manage.py showmigrations --shared
# Check tenant apps
python manage.py showmigrationspython manage.py showmigrations --schema=tenant_name# Shared apps
python manage.py migrate_schemas --shared <app_name> <previous_migration_number>
# Tenant apps
python manage.py migrate_schemas <app_name> <previous_migration_number>Example:
# Rollback employees app to migration 0002
python manage.py migrate_schemas employees 0002Warning: Rolling back migrations can cause data loss. Always backup before rolling back.
Migration files are stored in each app's migrations/ directory:
employees/
├── migrations/
│ ├── __init__.py
│ ├── 0001_initial.py
│ ├── 0002_employee_attributes.py
│ └── ...
- Keep migrations small: Break large changes into multiple migrations
- Test with real data: Test migrations with production-like data
- Document complex migrations: Add comments for complex data migrations
- Don't edit applied migrations: Never edit migrations that have been applied to production
- Use squashing for old migrations: Consider squashing old migrations to reduce migration count