Odoo.sh Migration
Migrate your Odoo instances from Odoo.sh, on-premise installations, or Odoo Online to the OEC.SH platform with a comprehensive migration workflow that preserves all your data, configurations, and customizations.
Migration Feature (Sprint 2E7): This feature enables seamless migration from Odoo.sh and other Odoo platforms with intelligent backup analysis, resource recommendations, and a 12-step deployment process.
Overview
The OEC.SH migration system provides a complete end-to-end solution for importing Odoo backups from various sources:
- Odoo.sh - Direct migration from Odoo's official PaaS
- On-Premise - Migrate self-hosted Odoo installations
- Odoo Online - Import from Odoo's SaaS offering
- Other PaaS - Migrate from alternative Odoo hosting providers
Key Features
- 4-Tier Upload System - Intelligent upload handling based on backup size (direct, chunked, multipart, accelerated)
- Cloud URL Import - Import directly from Google Drive, Dropbox, OneDrive
- Intelligent Analysis - Automatic detection of Odoo version, modules, database size, and resource requirements
- Resource Recommendations - AI-powered suggestions for CPU, RAM, and disk allocation
- Cost Comparison - See estimated monthly costs vs. Odoo.sh pricing
- 12-Step Deployment - Fully orchestrated deployment process with real-time progress tracking
- Data Loss Prevention - Migration restore flag prevents accidental data overwrites on redeployment
- Force Re-Restore - Optional capability to re-import backup with safeguards
Migration Workflow
High-Level Process
Timeline Expectations
| Phase | Duration | Description |
|---|---|---|
| Backup Export | 5-30 minutes | Export backup from Odoo.sh |
| Upload to Cloud | 10-60 minutes | Upload to S3/R2/B2 (depends on size) |
| Upload to OEC.SH | 5-120 minutes | Transfer from cloud URL (4-tier system) |
| Analysis | 2-10 minutes | Backup analysis and recommendations |
| Configuration | 5-15 minutes | Configure target server and resources |
| Deployment | 15-45 minutes | 12-step migration process |
| Total | ~1-5 hours | End-to-end migration time |
Downtime Considerations: Plan for a maintenance window. While the migration process is automated, your Odoo.sh instance should remain accessible until DNS is updated to point to the new OEC.SH environment.
Exporting from Odoo.sh
Creating a Backup on Odoo.sh
-
Access Your Odoo.sh Dashboard
- Navigate to odoo.sh (opens in a new tab)
- Select your project and branch
-
Create Production Backup
# Via Odoo.sh Web Interface: # 1. Go to Branches → Production # 2. Click "Backups" tab # 3. Click "Create Backup" button # 4. Wait for backup to complete (5-30 minutes) -
Download Backup
- Once backup completes, click "Download" icon
- Backup format:
.zipfile containing:dump.sql- PostgreSQL database dumpfilestore/- Directory with attachments and assetsmanifest.json- Metadata (Odoo version, modules, etc.)
Backup Format Compatibility
OEC.SH supports multiple backup formats:
| Format | Structure | Supported |
|---|---|---|
| Odoo.sh Standard | dump.sql + filestore/ + manifest.json in ZIP | ✅ Yes |
| Manual Export | dump.sql + filestore.tar.gz in ZIP | ✅ Yes |
| Database Only | dump.sql or .sql file | ✅ Yes (no filestore) |
| Custom Archive | .tar.gz or .tar with SQL dump | ✅ Yes |
Manifest.json: If your backup includes a manifest.json file from Odoo.sh, the analysis will be faster and more accurate as it contains authoritative metadata about your instance.
Upload to Cloud Storage
Before importing to OEC.SH, upload your backup to a supported cloud storage provider.
Supported Cloud Providers
- AWS S3 - Amazon Simple Storage Service
- Cloudflare R2 - S3-compatible, zero egress fees
- Backblaze B2 - Cost-effective S3-compatible storage
- Google Cloud Storage - With signed URL support
- DigitalOcean Spaces - S3-compatible object storage
Upload Process
Option 1: AWS S3
# Install AWS CLI
pip install awscli
# Configure credentials
aws configure
# Upload backup
aws s3 cp odoo-backup.zip s3://your-bucket/migrations/odoo-backup.zip
# Generate presigned URL (valid for 24 hours)
aws s3 presign s3://your-bucket/migrations/odoo-backup.zip --expires-in 86400Option 2: Cloudflare R2
# Install AWS CLI (R2 uses S3-compatible API)
pip install awscli
# Configure R2 endpoint
aws configure set aws_access_key_id YOUR_R2_ACCESS_KEY
aws configure set aws_secret_access_key YOUR_R2_SECRET_KEY
# Upload to R2
aws s3 cp odoo-backup.zip s3://your-bucket/migrations/odoo-backup.zip \\
--endpoint-url https://ACCOUNT_ID.r2.cloudflarestorage.com
# Generate presigned URL
aws s3 presign s3://your-bucket/migrations/odoo-backup.zip \\
--endpoint-url https://ACCOUNT_ID.r2.cloudflarestorage.com \\
--expires-in 86400Option 3: Direct Upload to OEC.SH
For backups under 5GB, you can upload directly via the OEC.SH migration wizard (no cloud storage required).
URL Requirements
- Must be a publicly accessible HTTPS URL
- Must return HTTP 200 with file content
- Should have
Content-Lengthheader for progress tracking - Presigned URLs must be valid for at least 1 hour
- Maximum file size: No hard limit (tested up to 50GB)
Migration Wizard (UI)
The OEC.SH web interface provides a step-by-step wizard for creating and executing migrations.
Step 1: Initialize Migration
- Navigate to Migrations in the left sidebar
- Click New Migration
- Fill in migration details:
// Migration Initialization Form
{
source_type: "odoosh" | "on_premise" | "odoo_online" | "other_paas",
project_name: "My Migrated Project",
odoosh_project_name: "original-odoosh-name", // Optional
odoosh_branch: "production", // Optional
git_repo_url: "https://github.com/company/custom-addons.git", // Optional
git_branch: "main", // Optional
data_processing_consent: true // Required (GDPR)
}- Data Processing Consent: You must consent to temporary storage of your backup on OEC.SH's secure cloud infrastructure (Cloudflare R2) for processing.
Step 2: Upload Method Selection
After creating the migration, choose your upload method:
A. Cloud URL Import (Recommended for large backups)
// POST /api/v1/migrations/{migration_id}/import-url
{
cloud_url: "https://your-bucket.s3.amazonaws.com/backup.zip?presigned-token"
}- Supports Google Drive, Dropbox, OneDrive, direct URLs
- Best for backups > 5GB
- Background download with progress tracking
- No browser upload limits
B. Direct Upload (< 50MB)
- Tier 1: Single HTTP POST
- Upload completes in one request
- Best for small test backups
C. Chunked Upload (50MB - 500MB)
- Tier 2: Upload in 10MB chunks
- Resumable if connection drops
- Progress tracking per chunk
D. Multipart Upload (500MB - 5GB)
- Tier 3: Cloudflare R2 multipart
- Parallel upload of parts
- Enterprise-grade reliability
E. Accelerated Upload (> 5GB)
- Tier 4: R2 multipart + acceleration
- Optimized for large databases
- Automatic retries and resumption
Step 3: Automatic Analysis
Once uploaded, OEC.SH automatically analyzes your backup:
- Duration: 2-10 minutes (depends on backup size)
- Process:
- Downloads backup from cloud storage
- Extracts archive (
.zip,.tar.gz,.tar) - Parses
manifest.json(if present) for metadata - Analyzes SQL dump for database metrics
- Scans filestore for attachment sizes
- Identifies installed modules (official vs. custom)
- Calculates resource recommendations
- Estimates monthly costs
Analysis Results Include:
- Odoo version (e.g., "17.0", "18.0")
- PostgreSQL version from backup
- Database name and size (bytes)
- Filestore size (bytes)
- Module list (official, OCA, custom)
- Partner/user counts
- Recommended CPU/RAM/disk
- Cost comparison (OEC.SH vs. Odoo.sh)
Step 4: Configure Target
After analysis completes, configure your target environment:
// Configuration Options
{
target_vm_id: "uuid-of-server", // Which server to deploy on
project_name: "Production ERP", // Override project name
target_domain: "erp.company.com", // Custom domain (optional)
git_repo_url: "https://...", // Override Git repo
git_branch: "main" // Override branch
}Server Selection:
- Only shows qualified servers in your organization
- Displays available resources (CPU, RAM, disk)
- Shows current utilization and capacity
Step 5: Start Migration
Click Start Migration to begin the 12-step deployment process. Progress is tracked in real-time via Server-Sent Events (SSE).
Migration API
For programmatic migrations, use the REST API.
Create Migration
POST /api/v1/migrations
Authorization: Bearer {jwt_token}
Content-Type: application/json
{
"organization_id": "uuid",
"source_type": "odoosh",
"project_name": "My ERP",
"odoosh_project_name": "original-name",
"odoosh_branch": "production",
"git_repo_url": "https://github.com/company/addons.git",
"git_branch": "main",
"data_processing_consent": true
}Response (201 Created):
{
"id": "migration-uuid",
"organization_id": "org-uuid",
"source_type": "odoosh",
"status": "pending",
"progress_percent": 0,
"project_name": "My ERP",
"created_at": "2024-12-11T10:00:00Z"
}Import from Cloud URL
POST /api/v1/migrations/{migration_id}/import-url
Authorization: Bearer {jwt_token}
Content-Type: application/json
{
"cloud_url": "https://bucket.s3.amazonaws.com/backup.zip?X-Amz-Signature=..."
}Response (202 Accepted):
{
"status": "importing",
"migration_id": "migration-uuid",
"message": "Import started. You will be notified when complete."
}Direct Upload (Tier 1)
POST /api/v1/migrations/{migration_id}/upload/init
Authorization: Bearer {jwt_token}
?file_name=backup.zip&file_size=45000000Response:
{
"migration_id": "uuid",
"upload_tier": "tier_1",
"upload_url": "/api/v1/migrations/uuid/upload/direct",
"method": "POST"
}Then upload file:
POST /api/v1/migrations/{migration_id}/upload/direct
Authorization: Bearer {jwt_token}
Content-Type: multipart/form-data
file=@backup.zipConfigure Migration
POST /api/v1/migrations/{migration_id}/configure
Authorization: Bearer {jwt_token}
Content-Type: application/json
{
"target_vm_id": "server-uuid",
"project_name": "Production ERP",
"target_domain": "erp.company.com"
}Start Migration Execution
POST /api/v1/migrations/{migration_id}/start
Authorization: Bearer {jwt_token}Response:
{
"status": "processing",
"migration_id": "uuid",
"message": "Migration started. Track progress via SSE or polling."
}Get Migration Status
GET /api/v1/migrations/{migration_id}
Authorization: Bearer {jwt_token}Response:
{
"id": "uuid",
"status": "processing",
"progress_percent": 45,
"current_step": "Restoring database from backup...",
"odoo_version_detected": "17.0",
"database_size_bytes": 2147483648,
"filestore_size_bytes": 5368709120,
"recommended_cpu": 2.0,
"recommended_ram_mb": 4096,
"estimated_monthly_cost": 59.0,
"odoosh_equivalent_cost": 199.0,
"monthly_savings": 140.0
}Get Analysis Results
GET /api/v1/migrations/{migration_id}/analysis
Authorization: Bearer {jwt_token}Response:
{
"migration_id": "uuid",
"odoo_version": "17.0",
"database_name": "production_db",
"database_size_bytes": 2147483648,
"filestore_size_bytes": 5368709120,
"total_size_bytes": 7516192768,
"total_modules": 87,
"odoo_modules": ["base", "web", "sale", "account", "stock"],
"custom_modules": ["custom_crm", "custom_reports"],
"partner_count": 15420,
"user_count": 42,
"recommended_cpu": 2.0,
"recommended_ram_mb": 4096,
"recommended_disk_gb": 25,
"estimated_monthly_cost": 59.0,
"odoosh_equivalent_cost": 199.0,
"monthly_savings": 140.0
}Migration Process
12-Step Deployment Flow
When you start a migration, OEC.SH executes a fully orchestrated 12-step process:
# Migration Steps (from migration_orchestrator.py)
STEPS = [
(5, "Creating project"), # 1. Create Project
(10, "Creating environment"), # 2. Create Environment
(20, "Deploying containers"), # 3. Deploy PostgreSQL + Odoo containers
(30, "Downloading backup from cloud"), # 4. Download backup from R2
(40, "Extracting backup archive"), # 5. Extract .zip/.tar.gz
(55, "Restoring database"), # 6. Restore PostgreSQL dump
(65, "Restoring filestore"), # 7. Restore attachments
(70, "Cloning Git repository"), # 8. Clone custom addons (if configured)
(80, "Configuring Traefik routing"), # 9. Setup SSL + reverse proxy
(85, "Restarting Odoo container"), # 10. Restart with restored data
(95, "Verifying deployment health"), # 11. Health checks
(100, "Migration complete") # 12. Mark complete
]Step-by-Step Breakdown
1. Create Project (5%)
- Creates a new
Projectrecord in your organization - Generates slug from project name
- Status:
CREATING→ACTIVE
2. Create Environment (10%)
- Creates
ProjectEnvironmentlinked to project - Assigns to target VM (server)
- Links to migration via
migration_idforeign key - Sets resource quotas (CPU, RAM, disk) from recommendations
- Status:
CREATING
3. Deploy Containers (20%)
- Creates Docker network for isolation
- Deploys PostgreSQL 15 container
- With PgBouncer for connection pooling
- Volume for persistent data
- Deploys Odoo container
- Based on detected Odoo version
- Configured with environment variables
- Not yet started (waiting for data)
4. Download Backup (30%)
- Generates presigned download URL from Cloudflare R2
- Downloads backup to VM's
/tmp/migration_{env_id}/ - Uses background curl to avoid blocking workers
- Polls for completion with progress tracking
- Timeout: 10 minutes max
- Validation: Checks file exists after download
5. Extract Backup (40%)
- Detects archive format (
.zip,.tar.gz,.tar) - Extracts to
/tmp/migration_{env_id}/extracted/ - Validates extracted contents:
dump.sqlor*.sqlfilefilestore/directory (optional)manifest.json(optional)
6. Restore Database (55%)
- Finds SQL dump file in extracted backup
- Copies dump to PostgreSQL container
- Stops Odoo container (releases DB connections)
- Terminates any remaining connections
- Drops existing database (if any)
- Creates fresh database
- Restores dump:
psql -U odoo -d dbname -f /tmp/dump.sql - Timeout: 10 minutes for restore (large DBs)
- Verifies restoration by checking
ir_module_moduletable
Example Restore Commands:
# Copy dump to container
docker cp /tmp/extracted/dump.sql postgres_container:/tmp/dump.sql
# Terminate connections
docker exec postgres_container psql -U odoo -d postgres -c \\
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'dbname';"
# Drop and recreate
docker exec postgres_container psql -U odoo -d postgres -c 'DROP DATABASE IF EXISTS "dbname";'
docker exec postgres_container psql -U odoo -d postgres -c 'CREATE DATABASE "dbname" OWNER odoo;'
# Restore
docker exec postgres_container psql -U odoo -d dbname -f /tmp/dump.sql7. Restore Filestore (65%)
- Finds
filestore/directory in backup - If found as
filestore.tar.gz, extracts first - Copies filestore to Odoo container:
/var/lib/odoo/filestore/{dbname}/ - Fixes ownership:
chown -R odoo:odoo /var/lib/odoo/filestore/ - Note: If no filestore in backup, step is skipped
8. Clone Repository (70%)
- If
git_repo_urlconfigured, clones custom addons - Clones to
/opt/paasportal/{env_id}/custom-addons - Checks out specified
git_branch - Skipped if: No Git repository configured
9. Configure Traefik (80%)
- Generates Traefik labels for routing
- Creates HTTP/HTTPS routing rules
- Configures SSL certificate (Let's Encrypt)
- Sets up WebSocket routing (Odoo 18+)
- Connects Odoo container to
traefik-publicnetwork
10. Restart Odoo (85%)
- Restarts Odoo container to pick up restored database
- Waits 3 seconds for initialization
- Container starts with correct configuration
11. Verify Deployment (95%)
- Health check: HTTP request to Odoo instance
- Verifies database connectivity
- Checks Odoo is responding
- Timeout: 30 seconds
- Retries: 3 attempts with exponential backoff
12. Complete (100%)
- Marks migration as
COMPLETED - Sets
migration_restore_completed = trueon environment - Records completion timestamp
- Broadcasts SSE event to organization
- Cleanup temporary files
SSE Progress Updates
Real-time progress is broadcast via Server-Sent Events:
// SSE Event Stream
// Connect to: GET /api/v1/events?organization_id={org_id}
// Event format
{
"event": "migration_progress",
"data": {
"migration_id": "uuid",
"status": "restoring_database",
"progress_percent": 55,
"current_step": "Restoring database from backup...",
"error_message": null
}
}Migration Environment
Migration ID Linkage
When a migration creates an environment, the relationship is tracked:
# ProjectEnvironment model
class ProjectEnvironment:
migration_id: UUID | None # Foreign key to migrations table
migration_restore_completed: bool = False
migration_restore_completed_at: datetime | NoneDatabase Schema:
ALTER TABLE project_environments
ADD COLUMN migration_id UUID REFERENCES migrations(id) ON DELETE SET NULL,
ADD COLUMN migration_restore_completed BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN migration_restore_completed_at TIMESTAMP WITH TIME ZONE;Special Handling for Migrated Environments
Environments created from migrations have special behavior:
-
First Deployment: During initial deployment, if
migration_idis set andmigration_restore_completed = false, the deployment flow automatically:- Downloads backup from R2
- Restores database
- Restores filestore
- Sets
migration_restore_completed = true
-
Subsequent Deployments: If
migration_restore_completed = true, redeployments preserve existing data instead of re-restoring backup.
Deployment Logic (from odoo_deployer.py):
# Line 725-737
if environment.migration_id and not environment.migration_restore_completed:
# Fetch migration backup and restore
migration = await db.get(Migration, environment.migration_id)
if migration and migration.file_name:
await self._restore_from_migration(config, db_password)
elif environment.migration_id and environment.migration_restore_completed:
logger.info(
f"Environment {env_id} migration restore already completed "
f"at {environment.migration_restore_completed_at}, "
"skipping restore to preserve data"
)Preventing Data Loss
The Problem: Accidental Re-Restore
Without safeguards, redeploying a migrated environment could accidentally re-download and restore the original backup, destroying all data added since migration.
Example Scenario:
Day 1: Migrate from Odoo.sh (database has 100 customers)
Day 30: Add 50 new customers (now 150 total)
Day 31: Redeploy environment for Odoo upgrade
Result WITHOUT protection: Database restored to Day 1 state (100 customers)
❌ LOST: 50 customers added in the last 30 days!The Solution: migration_restore_completed Flag
OEC.SH prevents this with a one-time restoration flag:
# After first successful restore
environment.migration_restore_completed = True
environment.migration_restore_completed_at = datetime.now()
await db.commit()How It Works:
-
First Deployment (migration restore):
migration_id = "abc-123" migration_restore_completed = False → RESTORES backup from R2 → Sets migration_restore_completed = True -
Second Deployment (redeploy):
migration_id = "abc-123" migration_restore_completed = True → SKIPS restore (preserves data) → Uses existing database
Benefits
- ✅ One-time restore: Backup is restored only on first deployment
- ✅ Data preservation: Redeployments preserve all data added post-migration
- ✅ Safe upgrades: Can redeploy for Odoo version upgrades without data loss
- ✅ Safe scaling: Can change resources without losing data
- ✅ Audit trail:
migration_restore_completed_attimestamp for compliance
Force Re-Restore
In rare cases, you may need to re-import the original migration backup, discarding all changes.
When You Might Need This
- Botched migration: Initial restore had issues, want to start fresh
- Test environment: Want to reset to original state for testing
- Data cleanup: Want to discard test data and restore production backup
- Rollback: Revert to pre-migration state (within backup retention)
DANGER: Force re-restore will PERMANENTLY DELETE all data added to the database since the original migration. This operation cannot be undone.
How to Force Re-Restore
Via API
Add force_restore=true query parameter to deployment endpoint:
POST /api/v1/projects/{project_id}/environments/{env_id}/deploy?force_restore=true
Authorization: Bearer {jwt_token}API Documentation (from deployments_routes.py):
@router.post("/{project_id}/environments/{env_id}/deploy")
async def deploy_environment(
force_restore: bool = Query(
False,
description="Force re-restore migration backup (WARNING: destroys current database!)"
),
):
"""
For migration environments where restore has already completed:
- By default, redeployment preserves the existing database
- Use force_restore=true to re-download and restore the original migration backup
WARNING: This will DESTROY all data added since the initial restore!
"""Implementation
When force_restore=true is passed:
# Line 2108-2115 (projects.py.backup)
if force_restore and environment.migration_id and environment.migration_restore_completed:
logger.warning(
f"force_restore=true: Clearing migration_restore_completed flag "
f"for environment {env_id}. Database will be re-restored from "
f"migration backup, DESTROYING all current data!"
)
environment.migration_restore_completed = False
environment.migration_restore_completed_at = None
await db.commit()This clears the flag, causing the next deployment to re-trigger the restore process.
UI Confirmation (Recommended)
While the backend supports force_restore via API, it's recommended to implement UI safeguards:
Type-to-Confirm Dialog:
// Example confirmation flow
const confirmRestore = async () => {
const confirmation = await showDialog({
title: "⚠️ Force Re-Restore Migration Backup",
message: `
This will PERMANENTLY DELETE all data added since the migration
and restore the original Odoo.sh backup.
This includes:
- All new customers, invoices, orders
- All configuration changes
- All uploaded files
Type 'RESTORE' to confirm this destructive action.
`,
requireTypedConfirmation: "RESTORE"
});
if (confirmation) {
await deployEnvironment(envId, { force_restore: true });
}
};Post-Migration Validation
After migration completes, perform these validation steps:
1. Health Checks
# Check Odoo is responding
curl -I https://your-env.apps.oec.sh
# Expected: HTTP/2 200 OK
# Check database connectivity
docker exec postgres_container psql -U odoo -d dbname -c "SELECT 1;"
# Expected: 1 row returned
# Check Odoo modules
docker exec odoo_container odoo shell -d dbname <<EOF
from odoo import registry
r = registry('dbname')
with r.cursor() as cr:
cr.execute("SELECT name, state FROM ir_module_module WHERE state='installed'")
print(cr.fetchall())
EOF2. Data Integrity Verification
Access Odoo and verify:
- ✅ Users: All users present and can log in
- ✅ Customers: Partner/customer records intact
- ✅ Transactions: Invoices, orders, quotations present
- ✅ Attachments: Files accessible (check filestore)
- ✅ Configurations: Company settings, fiscal positions, etc.
- ✅ Custom Modules: All custom addons functioning
3. Configuration Review
Check these settings in Odoo:
# System Parameters
Settings → Technical → Parameters → System Parameters
- web.base.url: Should be https://your-env.apps.oec.sh
- mail.catchall.domain: Update if needed
- mail.bounce.alias: Update if needed
# Email Configuration
Settings → General Settings → External Email Servers
- Update SMTP settings for new environment
- Test email sending
# Scheduled Actions
Settings → Technical → Automation → Scheduled Actions
- Verify cron jobs are running
- Check for failed jobs4. DNS and Domain Setup
If using a custom domain:
# Verify DNS propagation
dig your-domain.com
nslookup your-domain.com
# Check SSL certificate
curl -vI https://your-domain.com 2>&1 | grep "SSL certificate"
# Test from multiple locations
curl --resolve your-domain.com:443:SERVER_IP https://your-domain.com5. Performance Baseline
Establish performance metrics:
-- Database size
SELECT pg_database_size('dbname') / 1024 / 1024 AS size_mb;
-- Table sizes
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 10;
-- Connection count
SELECT count(*) FROM pg_stat_activity WHERE datname = 'dbname';Monitor container resources:
# CPU and memory usage
docker stats odoo_container postgres_container
# Disk usage
docker exec odoo_container df -h /var/lib/odooMigration vs Regular Restore
Key Differences
| Feature | Migration | Regular Restore |
|---|---|---|
| Source | Odoo.sh/external backup | OEC.SH backup created by platform |
| First-time flag | Uses migration_restore_completed | No special flag |
| Redeployment behavior | Skips restore after first time | Always restores from selected backup |
| Force re-restore | Requires force_restore=true | Default behavior |
| Linkage | environment.migration_id FK | backup.environment_id FK |
| Cost comparison | Shows Odoo.sh vs OEC.SH pricing | N/A |
| Analysis | Full backup analysis with recommendations | Uses backup manifest |
Migration: One-Time vs Ongoing Backup/Restore
Migration is a one-time operation:
Odoo.sh → [MIGRATION ONCE] → OEC.SH
↓
Use OEC.SH backups going forwardAfter Migration:
- Use OEC.SH's native backup system for future backups
- Backups created automatically (daily/weekly/monthly)
- Restore from OEC.SH backups (not original migration)
- Original migration backup kept in R2 for reference
Supported Odoo.sh Versions
Compatible Odoo Versions
OEC.SH supports migration from the following Odoo versions:
| Odoo Version | Status | Docker Image | Notes |
|---|---|---|---|
| 19.0 | ✅ Active | odoo:19.0 | Latest Community |
| 18.0 | ✅ Active | odoo:18.0 | Latest LTS |
| 17.0 | ✅ Active | odoo:17.0 | Previous LTS |
| 16.0 | ✅ Active | odoo:16.0 | Still supported |
| 15.0 | ⚠️ Deprecated | odoo:15.0 | Use with caution |
| 14.0 | ⚠️ Deprecated | odoo:14.0 | Use with caution |
| 13.0 | ⚠️ Deprecated | odoo:13.0 | Limited support |
Version Detection: The migration analyzer automatically detects your Odoo version from the backup's manifest.json or SQL dump. You don't need to specify it manually.
Version Mapping Considerations
Automatic Version Mapping:
- Backup version
17.0→ Deploys withodoo:17.0 - Backup version
18.0→ Deploys withodoo:18.0 - Backup version
19.0→ Deploys withodoo:19.0
Minor Version Handling:
17.0.1.0→ Treated as17.018.0.2.0→ Treated as18.0- Only major.minor version used for Docker image
Custom Images: Portal administrators can configure custom Docker images per version:
-- odoo_versions table
INSERT INTO odoo_versions (version, docker_image, status)
VALUES ('17.0', 'custom-registry.com/odoo:17.0-custom', 'active');Troubleshooting Migration
Download Failures from Cloud Storage
Symptoms:
- Migration stuck at "Downloading backup from cloud"
- Error: "Download timed out after 600s"
- Error: "Curl failed with exit code 56"
Solutions:
-
Check URL Expiration:
# Presigned URLs expire, regenerate if needed aws s3 presign s3://bucket/backup.zip --expires-in 7200 -
Verify Network Connectivity:
# From your server, test URL ssh root@server-ip curl -I "YOUR_PRESIGNED_URL" -
Check File Size:
# Very large files (>20GB) may need longer timeout # Contact support to increase timeout for large migrations -
Use Alternative Cloud Provider:
- Cloudflare R2: Zero egress fees, fast
- Backblaze B2: Cost-effective for large files
Incompatible Backup Format
Symptoms:
- Error: "No database dump file found in backup"
- Error: "Failed to extract backup"
Solutions:
-
Verify Backup Structure:
# Manually inspect backup unzip -l backup.zip # Should contain: dump.sql (or *.sql) # Optional: filestore/, manifest.json -
Re-export from Odoo.sh:
- Use Odoo.sh's native backup export
- Ensure backup includes database + filestore
-
Manual Backup Creation:
# Create compatible backup manually pg_dump -U odoo -Fp dbname > dump.sql tar -czf filestore.tar.gz /path/to/filestore/ zip backup.zip dump.sql filestore.tar.gz manifest.json
Large Database Migration Optimization
Symptoms:
- Database restore takes > 20 minutes
- Error: "Database restore timed out"
Solutions:
-
Pre-Migration Optimization:
-- On source Odoo.sh instance, run vacuum before backup VACUUM FULL ANALYZE; -- Remove old logs/transient data DELETE FROM mail_message WHERE create_date < NOW() - INTERVAL '90 days'; DELETE FROM ir_logging WHERE create_date < NOW() - INTERVAL '30 days'; -
Contact Support for Timeout Extension:
- For databases > 50GB, contact support
- We can increase restoration timeout limits
-
Split Migration:
- Option: Export database and filestore separately
- Upload database first, verify, then filestore
Network Timeout Issues
Symptoms:
- Migration fails during download
- Connection drops mid-transfer
Solutions:
-
Use Chunked Upload (Tier 2):
- For 50MB-500MB backups
- Automatically resumes if connection drops
-
Use Multipart (Tier 3/4):
- For 500MB+ backups
- Enterprise-grade reliability with retries
-
Direct Server Upload:
# If having issues with browser upload, use server-side # SSH to your OEC.SH server ssh root@server-ip # Download directly to server curl -o /tmp/backup.zip "YOUR_PRESIGNED_URL" # Upload to R2 from server aws s3 cp /tmp/backup.zip s3://paasportal-migrations/...
Migration Analysis Failures
Symptoms:
- Migration stuck at "Analyzing backup contents"
- Error: "Analysis failed: [error message]"
Solutions:
-
Check Backup Integrity:
# Test ZIP integrity unzip -t backup.zip # Test SQL dump head -100 dump.sql tail -100 dump.sql -
Manual Analysis Retry:
# Via API, trigger re-analysis POST /api/v1/migrations/{migration_id}/retry -
Skip Analysis (Advanced):
- Contact support to manually set analysis results
- Proceed to configuration step manually
Permissions
Required Permissions for Migration
| Action | Permission Code | Default Roles |
|---|---|---|
| Create Migration | org.migrations.create | org_owner, org_admin |
| View Migrations | org.migrations.list | All org members |
| Upload Backup | org.migrations.update | org_owner, org_admin |
| Configure Target | org.migrations.update | org_owner, org_admin |
| Execute Migration | org.migrations.execute | org_owner, org_admin |
| Delete Migration | org.migrations.delete | org_owner, org_admin |
Permission Checks in Backend
# Create migration (routes/migrations.py:139-149)
has_permission = await check_permission(
db=db,
user=current_user,
permission_code="org.migrations.create",
organization_id=data.organization_id,
)
# Execute migration (routes/migrations.py:671-681)
has_permission = await check_permission(
db=db,
user=current_user,
permission_code="org.migrations.execute",
organization_id=migration.organization_id,
)Organization Admin Requirements
Only organization administrators (with org.migrations.* permissions) can:
- Create new migrations
- Upload backup files
- Execute migrations
- Delete migration records
Regular organization members can:
- View migration list
- See migration progress
- View analysis results
API Reference
Complete Migration Endpoints
List Migrations
GET /api/v1/migrations?organization_id={uuid}&status=completed&page=1&page_size=20Query Parameters:
organization_id(required): Organization UUIDstatus: Filter by status (pending, uploading, analyzed, processing, completed, failed)page: Page number (default: 1)page_size: Items per page (default: 20, max: 100)
Response:
{
"items": [
{
"id": "uuid",
"source_type": "odoosh",
"status": "completed",
"progress_percent": 100,
"file_name": "backup.zip",
"odoo_version_detected": "17.0",
"created_at": "2024-12-11T10:00:00Z"
}
],
"total": 5,
"page": 1,
"page_size": 20,
"pages": 1
}Get Migration Details
GET /api/v1/migrations/{migration_id}Cancel Migration
POST /api/v1/migrations/{migration_id}/cancelResponse:
{
"status": "cancelled",
"migration_id": "uuid",
"message": "Migration cancelled successfully."
}Retry Failed Migration
POST /api/v1/migrations/{migration_id}/retryResponse:
{
"status": "ready",
"migration_id": "uuid",
"message": "Migration reset for retry. Call /start to begin.",
"retry_count": 1
}Delete Migration
DELETE /api/v1/migrations/{migration_id}Deletes migration record and associated R2 backup data.
Unlink Environment
POST /api/v1/migrations/{migration_id}/unlink-environmentDiscard linked environment and reset migration to analyzed state. Useful for starting over with a different configuration.
Response:
{
"status": "unlinked",
"migration_id": "uuid",
"environment_deleted": true,
"message": "Environment discarded. You can now configure a new deployment."
}Get Available VMs
GET /api/v1/migrations/{migration_id}/available-vmsReturns list of qualified servers in the organization suitable for deployment.
Response:
[
{
"id": "uuid",
"name": "Production Server 1",
"hostname": "165.22.65.97",
"cpu_cores": 8.0,
"memory_gb": 32.0,
"disk_gb": 500.0,
"available_cpu": 4.5,
"available_memory_gb": 18.0
}
]Best Practices
1. Test Migration on Staging First
Recommendation: Always perform a test migration before migrating production.
# Workflow
1. Export staging backup from Odoo.sh
2. Migrate to OEC.SH test environment
3. Verify functionality, data integrity
4. Document any issues or adjustments needed
5. Apply learnings to production migrationBenefits:
- Identify issues before production migration
- Estimate actual migration time
- Test custom module compatibility
- Validate DNS/domain setup
2. Verify Backup Integrity Before Migration
Before uploading to OEC.SH:
# Test ZIP integrity
unzip -t backup.zip
# Should output: "No errors detected"
# Verify SQL dump
head -n 100 dump.sql
# Should show: PostgreSQL dump header
# Check filestore (if extracted)
ls -lah filestore/
# Should contain database name subdirectory3. Plan for Downtime Window
Recommended Schedule:
Hour 0:00 - Announce maintenance window to users
Hour 0:15 - Export final backup from Odoo.sh
Hour 0:30 - Upload backup to cloud storage
Hour 0:45 - Start OEC.SH migration
Hour 2:30 - Migration complete, testing begins
Hour 3:00 - Update DNS to point to OEC.SH
Hour 3:30 - Verify DNS propagation
Hour 4:00 - Announce service restoredTotal Downtime: ~3-4 hours (conservative estimate)
4. Post-Migration Testing Checklist
After migration completes:
- Login Test: Verify users can log in
- Database Test: Check critical records (customers, invoices)
- Filestore Test: Open attachments, images work
- Email Test: Send test email from Odoo
- Cron Test: Verify scheduled actions running
- Integration Test: Test external API integrations
- Performance Test: Check page load times
- Search Test: Verify search functionality
- Report Test: Generate PDF reports
- Module Test: Test custom module features
5. Backup Original Migration Data
Keep your migration backup for 30 days:
- Store original
.zipfile locally - Keep presigned URL for re-download if needed
- Document migration date and settings
OEC.SH Retention:
- Migration backups stored in R2 indefinitely (unless deleted)
- Can re-download via API if needed
- Use "Force Re-Restore" if you need to reset
6. Update Integrations Post-Migration
After migration, update external systems:
# Update webhooks
Odoo Settings → Technical → Webhooks
- Update URLs from odoosh.com to apps.oec.sh
# Update API clients
- Update base URLs in API consumers
- Regenerate API keys if needed
# Update payment gateways
- Update return/callback URLs
- Re-verify gateway credentials
# Update email domains
- Update SPF/DKIM records if domain changed
- Test email deliverability7. Monitor First 48 Hours
Post-migration monitoring:
# Check container logs
docker logs -f odoo_container
docker logs -f postgres_container
# Monitor resource usage
docker stats
# Check database size growth
SELECT pg_database_size('dbname') / 1024 / 1024 AS size_mb;
# Monitor Odoo logs for errors
tail -f /var/log/odoo/odoo.logAlert Thresholds:
- CPU > 80% sustained
- Memory > 90% usage
- Disk > 85% full
- Response time > 3 seconds
8. Document Your Migration
Create Migration Documentation:
# Migration Log - [Project Name]
## Pre-Migration
- Source: Odoo.sh production
- Odoo Version: 17.0
- Database Size: 8.5 GB
- Filestore Size: 15.2 GB
- Users: 45
- Customers: 12,450
- Custom Modules: custom_crm, custom_reports
## Migration Details
- Date: 2024-12-11
- Duration: 3h 15m
- Target Server: Production Server 1
- Resources: 4 CPU, 8GB RAM, 80GB disk
- Migration ID: abc-123-def-456
## Post-Migration Changes
- Updated email SMTP settings
- Regenerated API keys
- Updated webhook URLs
- DNS cutover completed at 14:30 UTC
## Issues Encountered
- [None / List any issues]
## Follow-up Tasks
- [ ] Monitor performance for 48h
- [ ] Update documentation links
- [ ] Train users on new URL9. Cost Optimization
Right-sizing resources:
# Start with recommended resources
CPU: As suggested by analysis
RAM: As suggested by analysis
Disk: 3x total backup size
# Monitor actual usage for 1 week
# Scale down if consistently under 50% utilization
# Scale up if consistently over 80% utilizationExample Cost Comparison:
Odoo.sh Production + Staging: $199/month
OEC.SH Equivalent:
- 2 CPU, 4GB RAM: $59/month
- Custom domain: Included
- Backups: Included
Monthly Savings: $140 (70% reduction)10. Security Hardening
After migration:
# Change database passwords
docker exec postgres_container psql -U postgres -c \\
"ALTER USER odoo WITH PASSWORD 'new-secure-password';"
# Regenerate admin password
docker exec odoo_container odoo shell -d dbname <<EOF
from odoo import registry
r = registry('dbname')
with r.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
admin = env['res.users'].browse(SUPERUSER_ID)
admin.password = 'new-admin-password'
EOF
# Enable 2FA for admins
Settings → Users → [User] → Enable Two-Factor Auth
# Review user permissions
Settings → Users & Companies → Users
- Disable unused accounts
- Review access rightsSummary
The OEC.SH Migration system provides a production-ready solution for importing Odoo instances from Odoo.sh and other platforms:
✅ Intelligent Upload System: 4-tier upload (direct, chunked, multipart, accelerated) automatically selected based on backup size
✅ Comprehensive Analysis: Automatic detection of Odoo version, modules, sizes, and resource requirements with cost comparison
✅ 12-Step Orchestration: Fully automated deployment from project creation to verification with real-time progress tracking
✅ Data Loss Prevention: migration_restore_completed flag prevents accidental data overwrites on redeployment
✅ Force Re-Restore: Optional capability to re-import backup with type-to-confirm safeguards
✅ Enterprise-Grade Reliability: Background downloads, resumable uploads, automatic retries, comprehensive error handling
✅ Real-Time Monitoring: SSE progress updates with step-by-step status
✅ Permission System: Fine-grained RBAC for migration operations
For questions or issues with migrations, contact support at support@oec.sh or refer to the Troubleshooting section above.