Database Migration Rollback Procedure
Overview
This runbook provides step-by-step instructions for rolling back database migrations in the Almafrica platform. Use this procedure when you need to revert to a previous application version that requires an older database schema.
When to Use This Runbook
- Application rollback: When reverting to a previous application version that is incompatible with the current database schema
- Failed migration: When a migration causes issues in production and needs to be reversed
- Emergency recovery: When database changes need to be undone quickly
Prerequisites
Before starting a rollback, ensure you have:
- Database access: Connection string and credentials for the production database
- EF Core CLI:
dotnet eftools installed locally - Backup verification: Confirm that a recent database backup exists
- Target migration name: Know which migration to roll back to
- Team notification: Inform the team that a rollback is in progress
- Maintenance window: Schedule downtime if possible (recommended for production)
The RUN_MIGRATIONS Environment Variable
CRITICAL: The production environment has RunMigrations=false set in docker-compose.production.yml. This prevents the application from automatically running migrations on startup.
Why this matters for rollback:
- If you deploy an older application version, it won't see migrations that were added after that version
- Without the
RunMigrations=falsegate, the old app would fail or behave unpredictably - With the gate in place, you can safely deploy the old app, then manually roll back the database
For other environments:
- Development:
RunMigrations=true(default) - auto-migration enabled for convenience - Staging:
RunMigrations=true(default) - auto-migration enabled for testing - Production:
RunMigrations=false- migrations are manual and explicit
Step-by-Step Rollback Procedure
Step 1: Record Current State
Before making any changes, document the current state:
# Connect to production database and record current migration
cd backend/Almafrica.API
# List all applied migrations
dotnet ef migrations list --project ../Almafrica.Infrastructure --connection "<PRODUCTION_CONNECTION_STRING>"
# Record the last migration name (e.g., "20260210123456_AddFarmerPhoneNumber")
Save this information - you'll need it if you need to roll forward again.
Step 2: Identify Target Migration
Determine which migration to roll back to:
# Review migration history
dotnet ef migrations list --project ../Almafrica.Infrastructure
# Identify the target migration (the last "good" migration before the problematic one)
# Example: If rolling back from v1.2.0 to v1.1.0, find the last migration in v1.1.0
Target migration name format: 20260210123456_MigrationName
Step 3: Create Database Backup
MANDATORY: Always create a backup before rolling back:
# Using pg_dump (PostgreSQL)
pg_dump -h <host> -U <user> -d almafrica_production -F c -b -v -f "backup_before_rollback_$(date +%Y%m%d_%H%M%S).dump"
# Verify backup file exists and has reasonable size
ls -lh backup_before_rollback_*.dump
Step 4: Roll Back Database
Execute the migration rollback:
cd backend/Almafrica.API
# Roll back to target migration
dotnet ef database update <TargetMigrationName> \
--project ../Almafrica.Infrastructure \
--connection "<PRODUCTION_CONNECTION_STRING>"
# Example:
# dotnet ef database update 20260201120000_InitialCreate \
# --project ../Almafrica.Infrastructure \
# --connection "Host=db.example.com;Database=almafrica_prod;Username=admin;Password=***"
What happens:
- EF Core executes the
Down()method of each migration between the current state and the target - Migrations are rolled back in reverse chronological order
- Database schema is reverted to match the target migration
Step 5: Verify Database State
Confirm the rollback succeeded:
# List applied migrations - should show target migration as the last one
dotnet ef migrations list --project ../Almafrica.Infrastructure --connection "<PRODUCTION_CONNECTION_STRING>"
# Connect to database and verify schema
psql -h <host> -U <user> -d almafrica_production
# Check critical tables exist and have expected columns
\d farmers
\d orders
# etc.
Step 6: Deploy Previous Application Version
Now that the database matches the older schema, deploy the previous application version:
# Option A: Using Coolify UI
# 1. Go to Coolify dashboard
# 2. Select the API service
# 3. Choose "Redeploy" with the previous image tag (e.g., v1.1.0)
# Option B: Using Docker Compose
cd /path/to/deployment
export IMAGE_TAG=v1.1.0
docker compose -f docker-compose.production.yml pull
docker compose -f docker-compose.production.yml up -d api
# Option C: Using Coolify API
curl -X GET "https://coolify.example.com/api/v1/deploy?uuid=<API_UUID>&tag=v1.1.0" \
-H "Authorization: Bearer ${COOLIFY_TOKEN}"
Step 7: Verify Application Health
Confirm the application is running correctly:
# Check health endpoint
curl https://api.almafrica.com/health
# Check version endpoint
curl https://api.almafrica.com/health/version
# Verify version matches expected rollback version
# Expected: {"version": "1.1.0", "environment": "Production", ...}
# Check application logs for errors
docker logs almafrica-api --tail 100
Step 8: Smoke Test Critical Functionality
Perform basic smoke tests:
- Authentication: Test login with admin credentials
- Data retrieval: Fetch farmers, orders, or other critical entities
- Data creation: Create a test record (if safe to do so)
- API endpoints: Test key endpoints used by frontend/mobile
Step 9: Monitor and Communicate
After rollback:
- Monitor logs: Watch for errors or warnings
- Monitor metrics: Check API response times, error rates
- Notify team: Inform team that rollback is complete
- Update incident log: Document what happened and why rollback was needed
Migration Convention: Down() Methods
CRITICAL CONVENTION: All new migrations MUST have a working Down() method.
Why This Matters
- Rollback capability: Without
Down()methods, you cannot roll back migrations - Production safety: Enables quick recovery from failed deployments
- Testing: Allows testing migration rollback in staging before production
How to Ensure Down() Methods Work
When creating a new migration:
# 1. Create migration
dotnet ef migrations add AddNewFeature --project ../Almafrica.Infrastructure
# 2. Review the generated migration file
# Verify that the Down() method reverses what Up() does
# 3. Test the Down() method locally
dotnet ef database update AddNewFeature # Apply migration
dotnet ef database update PreviousMigration # Roll back
# 4. Verify database is in expected state after rollback
Down() Method Guidelines
- Reverse operations:
Down()should undo everythingUp()does - Data preservation: Consider data loss - add warnings if
Down()drops columns with data - Order matters: Drop foreign keys before dropping tables
- Idempotent:
Down()should be safe to run multiple times
Example:
public partial class AddFarmerPhoneNumber : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PhoneNumber",
table: "Farmers",
type: "character varying(20)",
maxLength: 20,
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PhoneNumber",
table: "Farmers");
}
}
Troubleshooting
Issue: Migration Rollback Fails
Symptoms: dotnet ef database update fails with constraint or data errors
Solutions:
- Check if there's data that prevents rollback (e.g., foreign key constraints)
- Manually clean up problematic data before retrying
- If
Down()method is missing or broken, manually write SQL to revert changes - Restore from backup if rollback is not possible
Issue: Application Fails After Rollback
Symptoms: Application starts but returns errors
Solutions:
- Verify database schema matches application expectations
- Check that correct application version is deployed
- Review application logs for specific errors
- Verify environment variables are correct
Issue: Data Loss After Rollback
Symptoms: Data created after the target migration is missing
Expected behavior: Rolling back migrations can cause data loss if:
- Columns are dropped that contained data
- Tables are dropped
- Data transformations are reversed
Prevention:
- Always backup before rollback
- Review
Down()methods for data-destructive operations - Consider manual data migration if needed
Emergency Contacts
If rollback fails or causes critical issues:
- Database Administrator: [Contact info]
- DevOps Lead: [Contact info]
- Project Lead: [Contact info]
Related Documentation
- Migration best practices:
agent-os/standards/backend/migrations.md - Migration checklist:
backend/MIGRATION_CHECKLIST.md - AGENTS.md: Migration conventions for the team
- Release process:
RELEASE.md(when available)
Rollback Checklist
Use this checklist when performing a rollback:
- Record current migration name
- Identify target migration
- Create database backup
- Verify backup file exists
- Execute
dotnet ef database update <target> - Verify database state
- Deploy previous application version
- Check health endpoint
- Check version endpoint
- Perform smoke tests
- Monitor logs for errors
- Notify team of completion
- Document incident and lessons learned
Post-Rollback Actions
After a successful rollback:
- Root cause analysis: Investigate why rollback was needed
- Fix the issue: Address the problem that caused the rollback
- Test thoroughly: Ensure fix works in staging before production
- Plan roll-forward: Determine when to deploy the fix
- Update documentation: Document lessons learned
Notes
- Rollback is not failure: It's a safety mechanism for production stability
- Practice in staging: Test rollback procedures in staging environment
- Keep it simple: Don't over-engineer - manual rollback with clear runbook is better than complex automation
- Communication is key: Keep team informed throughout the process