Features
Migrations
Odoo.sh Migration

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

PhaseDurationDescription
Backup Export5-30 minutesExport backup from Odoo.sh
Upload to Cloud10-60 minutesUpload to S3/R2/B2 (depends on size)
Upload to OEC.SH5-120 minutesTransfer from cloud URL (4-tier system)
Analysis2-10 minutesBackup analysis and recommendations
Configuration5-15 minutesConfigure target server and resources
Deployment15-45 minutes12-step migration process
Total~1-5 hoursEnd-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

  1. Access Your Odoo.sh Dashboard

  2. 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)
  3. Download Backup

    • Once backup completes, click "Download" icon
    • Backup format: .zip file containing:
      • dump.sql - PostgreSQL database dump
      • filestore/ - Directory with attachments and assets
      • manifest.json - Metadata (Odoo version, modules, etc.)

Backup Format Compatibility

OEC.SH supports multiple backup formats:

FormatStructureSupported
Odoo.sh Standarddump.sql + filestore/ + manifest.json in ZIP✅ Yes
Manual Exportdump.sql + filestore.tar.gz in ZIP✅ Yes
Database Onlydump.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 86400

Option 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 86400

Option 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-Length header 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

  1. Navigate to Migrations in the left sidebar
  2. Click New Migration
  3. 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)
}
  1. 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:
    1. Downloads backup from cloud storage
    2. Extracts archive (.zip, .tar.gz, .tar)
    3. Parses manifest.json (if present) for metadata
    4. Analyzes SQL dump for database metrics
    5. Scans filestore for attachment sizes
    6. Identifies installed modules (official vs. custom)
    7. Calculates resource recommendations
    8. 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
&#123;
  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
&#125;

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 &#123;jwt_token&#125;
Content-Type: application/json
 
&#123;
  "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
&#125;

Response (201 Created):

&#123;
  "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"
&#125;

Import from Cloud URL

POST /api/v1/migrations/&#123;migration_id&#125;/import-url
Authorization: Bearer &#123;jwt_token&#125;
Content-Type: application/json
 
&#123;
  "cloud_url": "https://bucket.s3.amazonaws.com/backup.zip?X-Amz-Signature=..."
&#125;

Response (202 Accepted):

&#123;
  "status": "importing",
  "migration_id": "migration-uuid",
  "message": "Import started. You will be notified when complete."
&#125;

Direct Upload (Tier 1)

POST /api/v1/migrations/&#123;migration_id&#125;/upload/init
Authorization: Bearer &#123;jwt_token&#125;
 
?file_name=backup.zip&file_size=45000000

Response:

&#123;
  "migration_id": "uuid",
  "upload_tier": "tier_1",
  "upload_url": "/api/v1/migrations/uuid/upload/direct",
  "method": "POST"
&#125;

Then upload file:

POST /api/v1/migrations/&#123;migration_id&#125;/upload/direct
Authorization: Bearer &#123;jwt_token&#125;
Content-Type: multipart/form-data
 
file=@backup.zip

Configure Migration

POST /api/v1/migrations/&#123;migration_id&#125;/configure
Authorization: Bearer &#123;jwt_token&#125;
Content-Type: application/json
 
&#123;
  "target_vm_id": "server-uuid",
  "project_name": "Production ERP",
  "target_domain": "erp.company.com"
&#125;

Start Migration Execution

POST /api/v1/migrations/&#123;migration_id&#125;/start
Authorization: Bearer &#123;jwt_token&#125;

Response:

&#123;
  "status": "processing",
  "migration_id": "uuid",
  "message": "Migration started. Track progress via SSE or polling."
&#125;

Get Migration Status

GET /api/v1/migrations/&#123;migration_id&#125;
Authorization: Bearer &#123;jwt_token&#125;

Response:

&#123;
  "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
&#125;

Get Analysis Results

GET /api/v1/migrations/&#123;migration_id&#125;/analysis
Authorization: Bearer &#123;jwt_token&#125;

Response:

&#123;
  "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
&#125;

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 Project record in your organization
  • Generates slug from project name
  • Status: CREATINGACTIVE

2. Create Environment (10%)

  • Creates ProjectEnvironment linked to project
  • Assigns to target VM (server)
  • Links to migration via migration_id foreign 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_&#123;env_id&#125;/
  • 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_&#123;env_id&#125;/extracted/
  • Validates extracted contents:
    • dump.sql or *.sql file
    • filestore/ 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_module table

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.sql

7. 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/&#123;dbname&#125;/
  • 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_url configured, clones custom addons
  • Clones to /opt/paasportal/&#123;env_id&#125;/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-public network

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 = true on 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=&#123;org_id&#125;
 
// Event format
&#123;
  "event": "migration_progress",
  "data": &#123;
    "migration_id": "uuid",
    "status": "restoring_database",
    "progress_percent": 55,
    "current_step": "Restoring database from backup...",
    "error_message": null
  &#125;
&#125;

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 | None

Database 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:

  1. First Deployment: During initial deployment, if migration_id is set and migration_restore_completed = false, the deployment flow automatically:

    • Downloads backup from R2
    • Restores database
    • Restores filestore
    • Sets migration_restore_completed = true
  2. 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 &#123;env_id&#125; migration restore already completed "
        f"at &#123;environment.migration_restore_completed_at&#125;, "
        "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:

  1. First Deployment (migration restore):

    migration_id = "abc-123"
    migration_restore_completed = False
    → RESTORES backup from R2
    → Sets migration_restore_completed = True
  2. 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_at timestamp 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/&#123;project_id&#125;/environments/&#123;env_id&#125;/deploy?force_restore=true
Authorization: Bearer &#123;jwt_token&#125;

API Documentation (from deployments_routes.py):

@router.post("/&#123;project_id&#125;/environments/&#123;env_id&#125;/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 &#123;env_id&#125;. 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 () => &#123;
  const confirmation = await showDialog(&#123;
    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"
  &#125;);
 
  if (confirmation) &#123;
    await deployEnvironment(envId, &#123; force_restore: true &#125;);
  &#125;
&#125;;

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())
EOF

2. 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 jobs

4. 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.com

5. 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/odoo

Migration vs Regular Restore

Key Differences

FeatureMigrationRegular Restore
SourceOdoo.sh/external backupOEC.SH backup created by platform
First-time flagUses migration_restore_completedNo special flag
Redeployment behaviorSkips restore after first timeAlways restores from selected backup
Force re-restoreRequires force_restore=trueDefault behavior
Linkageenvironment.migration_id FKbackup.environment_id FK
Cost comparisonShows Odoo.sh vs OEC.SH pricingN/A
AnalysisFull backup analysis with recommendationsUses 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 forward

After 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 VersionStatusDocker ImageNotes
19.0✅ Activeodoo:19.0Latest Community
18.0✅ Activeodoo:18.0Latest LTS
17.0✅ Activeodoo:17.0Previous LTS
16.0✅ Activeodoo:16.0Still supported
15.0⚠️ Deprecatedodoo:15.0Use with caution
14.0⚠️ Deprecatedodoo:14.0Use with caution
13.0⚠️ Deprecatedodoo:13.0Limited 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 with odoo:17.0
  • Backup version 18.0 → Deploys with odoo:18.0
  • Backup version 19.0 → Deploys with odoo:19.0

Minor Version Handling:

  • 17.0.1.0 → Treated as 17.0
  • 18.0.2.0 → Treated as 18.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:

  1. Check URL Expiration:

    # Presigned URLs expire, regenerate if needed
    aws s3 presign s3://bucket/backup.zip --expires-in 7200
  2. Verify Network Connectivity:

    # From your server, test URL
    ssh root@server-ip
    curl -I "YOUR_PRESIGNED_URL"
  3. Check File Size:

    # Very large files (>20GB) may need longer timeout
    # Contact support to increase timeout for large migrations
  4. 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:

  1. Verify Backup Structure:

    # Manually inspect backup
    unzip -l backup.zip
    # Should contain: dump.sql (or *.sql)
    # Optional: filestore/, manifest.json
  2. Re-export from Odoo.sh:

    • Use Odoo.sh's native backup export
    • Ensure backup includes database + filestore
  3. 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:

  1. 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';
  2. Contact Support for Timeout Extension:

    • For databases > 50GB, contact support
    • We can increase restoration timeout limits
  3. 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:

  1. Use Chunked Upload (Tier 2):

    • For 50MB-500MB backups
    • Automatically resumes if connection drops
  2. Use Multipart (Tier 3/4):

    • For 500MB+ backups
    • Enterprise-grade reliability with retries
  3. 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:

  1. Check Backup Integrity:

    # Test ZIP integrity
    unzip -t backup.zip
     
    # Test SQL dump
    head -100 dump.sql
    tail -100 dump.sql
  2. Manual Analysis Retry:

    # Via API, trigger re-analysis
    POST /api/v1/migrations/&#123;migration_id&#125;/retry
  3. Skip Analysis (Advanced):

    • Contact support to manually set analysis results
    • Proceed to configuration step manually

Permissions

Required Permissions for Migration

ActionPermission CodeDefault Roles
Create Migrationorg.migrations.createorg_owner, org_admin
View Migrationsorg.migrations.listAll org members
Upload Backuporg.migrations.updateorg_owner, org_admin
Configure Targetorg.migrations.updateorg_owner, org_admin
Execute Migrationorg.migrations.executeorg_owner, org_admin
Delete Migrationorg.migrations.deleteorg_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=&#123;uuid&#125;&status=completed&page=1&page_size=20

Query Parameters:

  • organization_id (required): Organization UUID
  • status: Filter by status (pending, uploading, analyzed, processing, completed, failed)
  • page: Page number (default: 1)
  • page_size: Items per page (default: 20, max: 100)

Response:

&#123;
  "items": [
    &#123;
      "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"
    &#125;
  ],
  "total": 5,
  "page": 1,
  "page_size": 20,
  "pages": 1
&#125;

Get Migration Details

GET /api/v1/migrations/&#123;migration_id&#125;

Cancel Migration

POST /api/v1/migrations/&#123;migration_id&#125;/cancel

Response:

&#123;
  "status": "cancelled",
  "migration_id": "uuid",
  "message": "Migration cancelled successfully."
&#125;

Retry Failed Migration

POST /api/v1/migrations/&#123;migration_id&#125;/retry

Response:

&#123;
  "status": "ready",
  "migration_id": "uuid",
  "message": "Migration reset for retry. Call /start to begin.",
  "retry_count": 1
&#125;

Delete Migration

DELETE /api/v1/migrations/&#123;migration_id&#125;

Deletes migration record and associated R2 backup data.

Unlink Environment

POST /api/v1/migrations/&#123;migration_id&#125;/unlink-environment

Discard linked environment and reset migration to analyzed state. Useful for starting over with a different configuration.

Response:

&#123;
  "status": "unlinked",
  "migration_id": "uuid",
  "environment_deleted": true,
  "message": "Environment discarded. You can now configure a new deployment."
&#125;

Get Available VMs

GET /api/v1/migrations/&#123;migration_id&#125;/available-vms

Returns list of qualified servers in the organization suitable for deployment.

Response:

[
  &#123;
    "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
  &#125;
]

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 migration

Benefits:

  • 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 subdirectory

3. 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 restored

Total 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 .zip file 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 deliverability

7. 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.log

Alert 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 URL

9. 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% utilization

Example 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, &#123;&#125;)
    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 rights

Summary

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.