Backup & Recovery¶
Backup strategy and disaster recovery procedures for JDX Odoo.
Understanding Odoo Data Storage¶
Odoo stores data in two separate locations:
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL Database │
│ │
│ - All users and passwords │
│ - All settings and configurations │
│ - Installed modules list │
│ - All business data (invoices, orders, contacts) │
│ - File references (paths only, not actual files) │
└─────────────────────────────────────────────────────────────┘
+
┌─────────────────────────────────────────────────────────────┐
│ Filestore │
│ │
│ - Attachments (PDFs, images, documents) │
│ - Report templates │
│ - Email attachments │
│ - Product images │
└─────────────────────────────────────────────────────────────┘
How Database Links to Filestore¶
The database stores file paths, not actual files. Odoo links them at runtime:
Database (ir_attachment table):
┌─────────┬──────────────────┬─────────────────────────┐
│ id │ name │ store_fname │
├─────────┼──────────────────┼─────────────────────────┤
│ 1 │ invoice.pdf │ ab/ab123456789... │
│ 2 │ photo.jpg │ cd/cd987654321... │
└─────────┴──────────────────┴─────────────────────────┘
↓
Filestore folder:
/var/lib/odoo/.local/share/Odoo/filestore/{database_name}/
├── ab/
│ └── ab123456789... ← actual PDF file
└── cd/
└── cd987654321... ← actual JPG file
Database name must match filestore folder
Odoo looks for files at: {data_dir}/filestore/{DATABASE_NAME}/{store_fname}
If names don't match, attachments won't load.
Filestore Locations¶
| Installation Type | Filestore Path |
|---|---|
| Native (package) | /var/lib/odoo/filestore/ |
| Native (source) | /var/lib/odoo/.local/share/Odoo/filestore/ |
| Docker | /var/lib/odoo/filestore/ (inside container) |
| Custom | Check data_dir in odoo.conf |
Finding Your Filestore¶
# Check odoo.conf for data_dir
sudo cat /etc/odoo/odoo.conf | grep data_dir
# Example output:
# data_dir = /var/lib/odoo/.local/share/Odoo
# Filestore is at: {data_dir}/filestore/{database_name}/
sudo ls -la /var/lib/odoo/.local/share/Odoo/filestore/
# Check size
sudo du -sh /var/lib/odoo/.local/share/Odoo/filestore/
odoo.conf Configuration¶
Key settings in /etc/odoo/odoo.conf:
[options]
; Database connection (for RDS)
db_host = your-rds-endpoint.amazonaws.com
db_port = 5432
db_user = odoo
db_password = your_password
db_name = production
; Data directory (contains filestore)
data_dir = /var/lib/odoo/.local/share/Odoo
; Addons path
addons_path = /opt/odoo/addons,/opt/odoo/custom-addons
Backup Strategy¶
What Gets Backed Up¶
| Component | Method | Frequency | Retention |
|---|---|---|---|
| PostgreSQL Database | pg_dump | Daily | 30 days |
| Odoo Filestore | tar archive | Daily | 30 days |
| Configuration | Git | On change | Forever |
Backup Schedule¶
| Type | Schedule | Retention |
|---|---|---|
| Full backup | Daily 2:00 AM | 30 days |
| Database only | Every 6 hours | 7 days |
| S3 lifecycle | Auto-expire | 90 days → Glacier |
Backup Scripts¶
Full Backup¶
Creates:
- odoo_db_YYYYMMDD_HHMMSS.sql.gz
- odoo_filestore_YYYYMMDD_HHMMSS.tar.gz
Uploads to S3 bucket.
Database Only¶
Filestore Only¶
Manual Backup¶
# Database
docker compose exec db pg_dump -U odoo odoo_test | gzip > backup.sql.gz
# Filestore
docker compose exec odoo tar -czf - -C /var/lib/odoo filestore > filestore.tar.gz
Restore Procedures¶
Full Restore¶
This will: 1. Stop Odoo service 2. Drop existing database 3. Restore database from backup 4. Restore filestore 5. Restart Odoo
Database Only Restore¶
Manual Restore¶
# Stop Odoo
docker compose stop odoo
# Drop and recreate database
docker compose exec db psql -U odoo -d postgres -c "DROP DATABASE IF EXISTS odoo_test;"
docker compose exec db psql -U odoo -d postgres -c "CREATE DATABASE odoo_test OWNER odoo;"
# Restore
gunzip -c backup.sql.gz | docker compose exec -T db psql -U odoo -d odoo_test
# Start Odoo
docker compose start odoo
Native Odoo Restore (AWS EC2 with RDS)¶
For native Odoo installations using AWS Lightsail/RDS PostgreSQL:
Migration to New Server¶
Since database is on RDS (separate from EC2), you only need to:
- Restore filestore files
- Point odoo.conf to existing RDS
┌─────────────────┐ ┌─────────────────┐
│ New EC2 │ │ Lightsail RDS │
│ │ ──────► │ PostgreSQL │
│ - Odoo app │ │ │
│ - Filestore │ │ - Database │
│ (restore here) │ │ (already has │
│ │ │ your data) │
└─────────────────┘ └─────────────────┘
Step-by-Step New Server Setup¶
# 1. Install Odoo (no PostgreSQL needed - using RDS)
sudo apt update
sudo apt install odoo
# 2. Stop Odoo before configuration
sudo systemctl stop odoo
# 3. Configure odoo.conf with RDS connection
sudo nano /etc/odoo/odoo.conf
[options]
; RDS connection
db_host = your-rds-endpoint.lightsail.amazonaws.com
db_port = 5432
db_user = odoo
db_password = your_rds_password
db_name = production
; Data directory
data_dir = /var/lib/odoo/.local/share/Odoo
; Addons
addons_path = /opt/odoo/addons,/opt/odoo/custom-addons
# 4. Create filestore directory
sudo mkdir -p /var/lib/odoo/.local/share/Odoo/filestore
# 5. Restore filestore from backup
sudo tar -xzvf filestore-backup.tar.gz -C /var/lib/odoo/.local/share/Odoo/filestore/
# 6. Fix permissions
sudo chown -R odoo:odoo /var/lib/odoo/.local/share/Odoo
# 7. Start Odoo
sudo systemctl start odoo
# 8. Verify
sudo systemctl status odoo
sudo tail -f /var/log/odoo/odoo.log
Full Restore (Database + Filestore)¶
If restoring to a completely new RDS as well:
# 1. Install Odoo
sudo apt install odoo
# 2. Stop Odoo
sudo systemctl stop odoo
# 3. Restore database to RDS
# Connect to RDS from EC2
PGPASSWORD=your_password psql -h your-rds-endpoint.amazonaws.com -U odoo -d postgres
# Create database (name must match filestore folder)
CREATE DATABASE production OWNER odoo;
\q
# Restore SQL dump
PGPASSWORD=your_password psql -h your-rds-endpoint.amazonaws.com -U odoo -d production < backup.sql
# 4. Restore filestore (folder name must match database name)
sudo tar -xzvf filestore-backup.tar.gz -C /var/lib/odoo/.local/share/Odoo/filestore/
# Verify folder name matches
ls /var/lib/odoo/.local/share/Odoo/filestore/
# Should show: production
# 5. Fix permissions
sudo chown -R odoo:odoo /var/lib/odoo/.local/share/Odoo
# 6. Configure odoo.conf (see above)
# 7. Start Odoo
sudo systemctl start odoo
Backup Commands for Native Odoo¶
# Backup database from RDS
PGPASSWORD=your_password pg_dump -h your-rds-endpoint.amazonaws.com -U odoo production > backup-$(date +%Y%m%d).sql
# Backup filestore
sudo tar -czvf filestore-$(date +%Y%m%d).tar.gz -C /var/lib/odoo/.local/share/Odoo/filestore production
# Upload to S3
aws s3 cp backup-$(date +%Y%m%d).sql s3://your-bucket/backups/
aws s3 cp filestore-$(date +%Y%m%d).tar.gz s3://your-bucket/backups/
Sync Filestore to S3 (Incremental)¶
For ongoing filestore backup:
# Sync only changed files (efficient)
aws s3 sync /var/lib/odoo/.local/share/Odoo/filestore/production s3://your-bucket/filestore/
# Add to cron for weekly backup
crontab -e
# Add: 0 3 * * 0 aws s3 sync /var/lib/odoo/.local/share/Odoo/filestore/production s3://your-bucket/filestore/
S3 Backup Configuration¶
Bucket Setup¶
# Create bucket
aws s3 mb s3://jdx-odoo-backups --region us-east-1
# Enable versioning
aws s3api put-bucket-versioning \
--bucket jdx-odoo-backups \
--versioning-configuration Status=Enabled
# Enable encryption
aws s3api put-bucket-encryption \
--bucket jdx-odoo-backups \
--server-side-encryption-configuration '{
"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]
}'
Lifecycle Policy¶
{
"Rules": [
{
"ID": "BackupRetention",
"Status": "Enabled",
"Filter": {"Prefix": "backups/"},
"Transitions": [
{"Days": 30, "StorageClass": "STANDARD_IA"},
{"Days": 90, "StorageClass": "GLACIER"}
],
"Expiration": {"Days": 365}
}
]
}
S3 Cost Estimation¶
Storage Calculation¶
Based on typical Odoo installation:
| Backup Type | Size | Frequency | Retention | Total Storage |
|---|---|---|---|---|
| Full backup (DB+files) | ~900MB | Daily | 30 days | 27 GB |
| Database only | ~100MB | 4x/day | 7 days | 2.8 GB |
| Total S3 Standard | ~30 GB |
AWS S3 Pricing (US East)¶
| Storage Class | Price/GB/Month | 30GB Monthly Cost |
|---|---|---|
| S3 Standard | $0.023 | $0.69 |
| S3 Standard-IA | $0.0125 | $0.38 |
| S3 Glacier | $0.004 | $0.12 |
Monthly Cost Breakdown¶
| Item | Cost |
|---|---|
| S3 Standard (recent 30 days) | $0.69 |
| S3 Standard-IA (30-90 days old) | $0.38 |
| S3 Glacier (90-365 days old) | $0.12 |
| PUT requests (~150/month) | $0.001 |
| Total Monthly | ~$1.20 |
Annual Cost¶
| Period | Storage Mix | Est. Cost |
|---|---|---|
| Year 1 (building up) | Mixed tiers | ~$15-20 |
| Year 2+ (steady state) | Mostly Glacier | ~$12-15 |
Cost-Effective Backup
Full disaster recovery coverage for approximately $1.20/month makes S3 backup extremely cost-effective compared to alternatives.
Disaster Recovery¶
Recovery Time Objectives¶
| Scenario | RTO | RPO |
|---|---|---|
| Service restart | 5 min | 0 |
| Container failure | 10 min | 0 |
| Database corruption | 30 min | 6 hours |
| Complete server failure | 2 hours | 24 hours |
| Region failure | 4 hours | 24 hours |
DR Scenarios¶
Scenario 1: Container Failure¶
Scenario 2: Database Corruption¶
# 1. Stop services
docker compose stop odoo pwa
# 2. Identify latest backup
aws s3 ls s3://jdx-odoo-backups/backups/ --recursive | tail -5
# 3. Download backup
aws s3 cp s3://jdx-odoo-backups/backups/odoo_db_latest.sql.gz .
# 4. Restore
./scripts/restore.sh odoo_db_latest.sql.gz
Scenario 3: Complete Server Failure¶
# 1. Launch new server with same specs
# 2. Install Docker
# 3. Clone repository
git clone git@github.com:yourorg/odoo15-production.git
cd odoo15-production
# 4. Configure environment
cp .env.example .env
# Edit .env with production settings:
# - Change COMPOSE_FILE to: docker-compose.yml:docker-compose.prod.yml
# - Set RDS connection details
# - Set production passwords
# 5. Download latest backup
aws s3 cp s3://jdx-odoo-backups/backups/odoo_db_latest.sql.gz .
aws s3 cp s3://jdx-odoo-backups/backups/odoo_filestore_latest.tar.gz .
# 6. Start services (COMPOSE_FILE in .env auto-loads both files)
docker compose up -d db
sleep 10
# 7. Restore data
./scripts/restore.sh odoo_db_latest.sql.gz odoo_filestore_latest.tar.gz
# 8. Start remaining services
docker compose up -d
# 9. Update DNS to new server IP
Testing Backups¶
Monthly DR Test¶
- Spin up test environment
- Download production backup
- Restore to test environment
- Verify:
- Login works
- Recent data present
- File attachments accessible
- Custom modules function
- Document results
Test Script¶
#!/bin/bash
# DR Test Script
echo "Starting DR test..."
# Create test environment (COMPOSE_FILE in .env auto-loads both files)
docker compose up -d db
sleep 10
# Restore latest backup
./scripts/restore.sh /tmp/latest-backup.sql.gz
# Run health check
./scripts/health-check.sh
echo "DR test complete. Verify manually at http://localhost:8016"
Backup Verification¶
Daily Checks¶
- Backup job completed
- Files uploaded to S3
- No errors in logs
Weekly Checks¶
- Verify backup file sizes are reasonable
- Test download speed from S3
- Check S3 storage costs
Monthly Checks¶
- Full restore test
- Verify all data integrity
- Update DR documentation