Skip to the content.

Runbook — Backup and Restore

Last Updated: March 2026 Owner: Ezinna (Founder) Applies to: Phase 2 onwards (local) — Phase 3 (cloud)


Overview

AgriOps stores commercially sensitive supply chain data and EUDR compliance records. Data loss is not recoverable from the application — it must be prevented through regular, verified backups. This runbook documents the backup strategy, backup procedures, restore procedures, and backup verification process.

Backup philosophy: A backup that has not been tested is not a backup. Every backup procedure includes a verification step.


What Must Be Backed Up

Data Location Criticality Notes
PostgreSQL database DB server Critical All application data
Compliance documents media/compliance_docs/ Critical Uploaded files — farm maps, certifications
Environment configuration .env Critical Never commit to git — back up securely
Application code GitHub High Already version controlled

Backup Strategy

Phase 2 — Local Development

Database: Manual backup before any significant migration or data change.

Frequency: Before every Phase 2 build session and before any migration.

Retention: Keep last 5 backups locally.


Local Backup Procedures

PostgreSQL Database Backup

# Create backup directory if it doesn't exist
mkdir -p ~/agri_ops_backups

# Create a timestamped backup
pg_dump -U postgres -d agri_ops_db -F c -f ~/agri_ops_backups/agriops_$(date +%Y%m%d_%H%M%S).dump

# Verify the backup was created and has a reasonable size
ls -lh ~/agri_ops_backups/

Expected output: a .dump file with non-zero size. A zero-byte file means the backup failed.

Verify the backup is readable

# List the contents of the backup without restoring it
pg_restore --list ~/agri_ops_backups/agriops_YYYYMMDD_HHMMSS.dump | head -30

If this command produces output, the backup file is valid. If it errors, the backup is corrupt — take another one.

Media files backup (compliance documents)

# Backup the media directory
tar -czf ~/agri_ops_backups/agriops_media_$(date +%Y%m%d_%H%M%S).tar.gz ~/agri_ops/media/

# Verify
tar -tzf ~/agri_ops_backups/agriops_media_YYYYMMDD_HHMMSS.tar.gz | head -20

Restore Procedures

Restore PostgreSQL Database from Backup

Warning: This will overwrite all data in the target database. Only proceed if you understand the consequences.

# Step 1 — Stop the application
pkill -f "manage.py runserver"

# Step 2 — Drop and recreate the database
psql -U postgres -c "DROP DATABASE agri_ops_db;"
psql -U postgres -c "CREATE DATABASE agri_ops_db;"

# Step 3 — Restore from backup
pg_restore -U postgres -d agri_ops_db ~/agri_ops_backups/agriops_YYYYMMDD_HHMMSS.dump

# Step 4 — Verify restoration
psql -U postgres -d agri_ops_db -c "SELECT COUNT(*) FROM companies_company;"
psql -U postgres -d agri_ops_db -c "SELECT COUNT(*) FROM suppliers_supplier;"
psql -U postgres -d agri_ops_db -c "SELECT COUNT(*) FROM users_customuser;"

# Step 5 — Run Django check
cd ~/agri_ops
source venv/bin/activate
python manage.py check

# Step 6 — Restart application
python manage.py runserver 8001

Restore Media Files

# Restore compliance documents
cd ~/agri_ops
tar -xzf ~/agri_ops_backups/agriops_media_YYYYMMDD_HHMMSS.tar.gz

# Verify key directories exist
ls media/compliance_docs/

Phase 3 — Cloud Backup Strategy

When deployed to Railway or Render, the following backup strategy applies:

Automated Database Backups

Provider-managed backups:

Supplement with manual backups:

# Connect to production database and dump remotely
pg_dump $DATABASE_URL -F c -f backups/agriops_prod_$(date +%Y%m%d).dump

Media Files — Cloud Storage (Phase 3)

Compliance documents will be stored in cloud object storage (AWS S3 or equivalent) with:

Backup Schedule (Phase 3 Target)

Backup Type Frequency Retention Storage
Full database Daily 30 days Provider + off-site
Media files On upload (versioned) Indefinite S3 with versioning
Configuration On change Indefinite Encrypted secure notes

Backup Verification Schedule

Backups are only useful if they can be restored. Test restores must be performed regularly.

Test Frequency Procedure
Backup file integrity Every backup pg_restore --list on dump file
Full restore to test DB Monthly Restore to agri_ops_db_test, verify record counts
Media file restore Monthly Extract to temp directory, spot-check files

Monthly restore test procedure

# Create a test database
psql -U postgres -c "CREATE DATABASE agri_ops_db_restore_test;"

# Restore latest backup to test database
pg_restore -U postgres -d agri_ops_db_restore_test ~/agri_ops_backups/latest.dump

# Verify record counts match production
psql -U postgres -d agri_ops_db -c "SELECT COUNT(*) FROM suppliers_supplier;" 
psql -U postgres -d agri_ops_db_restore_test -c "SELECT COUNT(*) FROM suppliers_supplier;"
# Counts must match

# Clean up test database
psql -U postgres -c "DROP DATABASE agri_ops_db_restore_test;"

Pre-Migration Backup Checklist

Before running any Django migration that modifies existing tables:

# 1. Take a database backup
pg_dump -U postgres -d agri_ops_db -F c -f ~/agri_ops_backups/pre_migration_$(date +%Y%m%d_%H%M%S).dump

# 2. Verify the backup
pg_restore --list ~/agri_ops_backups/pre_migration_*.dump | wc -l
# Should return a non-zero number

# 3. Note the current record counts
psql -U postgres -d agri_ops_db -c "
SELECT
  'companies' as table, COUNT(*) FROM companies_company
UNION ALL SELECT 'users', COUNT(*) FROM users_customuser
UNION ALL SELECT 'suppliers', COUNT(*) FROM suppliers_supplier
UNION ALL SELECT 'products', COUNT(*) FROM products_product;
"

# 4. Only then run the migration
python manage.py migrate

# 5. Verify record counts are unchanged
# Re-run the query above and confirm counts match

Disaster Recovery Targets (Phase 3+)

Metric Target Notes
RPO (Recovery Point Objective) 24 hours Maximum data loss acceptable
RTO (Recovery Time Objective) 4 hours Maximum time to restore service

These targets will be validated through quarterly disaster recovery drills from Phase 3 onwards.