Skip to content

How to Set Up an Odoo Staging Environment

DeployMonkey Team · March 11, 2026 8 min read

Why You Need a Staging Environment

Every Odoo update — whether a module upgrade, a configuration change, or a custom development — should be tested on a copy of production before touching the live database. A staging environment is not optional for business-critical installations; it is what separates teams that have Odoo outages from teams that do not.

Architecture: Separate Docker Stack

The simplest approach is a second Docker Compose stack on the same server (or a separate staging server), pointing to a cloned database:

Production:  ports 8069/8072  ->  db: mydb_prod
Staging:     ports 8070/8073  ->  db: mydb_staging

Both stacks can share the same PostgreSQL instance if your server has enough RAM.

Step 1 — Clone the Production Database

# Option A: pg_dump / pg_restore (safest, works across versions)
pg_dump -U odoo -Fc mydb_prod > /tmp/prod_backup.dump
createdb -U odoo mydb_staging
pg_restore -U odoo -d mydb_staging /tmp/prod_backup.dump

# Option B: PostgreSQL createdb with template (fast, same server only)
createdb -U odoo -T mydb_prod mydb_staging

# Option C: Odoo database manager (UI)
# Database Manager > Duplicate > enter new name

Step 2 — Create the Staging Docker Compose File

# docker-compose.staging.yml
version: "3.8"
services:
  odoo_staging:
image: odoo:19
ports:
  - "127.0.0.1:8070:8069"
  - "127.0.0.1:8073:8072"
volumes:
  - ./staging_data:/var/lib/odoo
  - ./addons:/mnt/extra-addons
  - ./staging.conf:/etc/odoo/odoo.conf
depends_on:
  - db

  db:
image: postgres:16
# (or reuse your existing db container if on same network)
# staging.conf
[options]
db_name = mydb_staging
db_host = db
workers = 2
list_db = False

Step 3 — Configure a Staging Subdomain

# nginx: staging.example.com -> 127.0.0.1:8070
server {
listen 443 ssl;
server_name staging.odoo.example.com;

location / {
    proxy_pass http://127.0.0.1:8070;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}
}

Get a free SSL certificate with Certbot: certbot --nginx -d staging.odoo.example.com

Step 4 — Anonymise Sensitive Data

Production data should never be accessible to developers who do not have production access. Anonymise it after cloning:

# Connect to Odoo shell on staging:
python odoo-bin shell -d mydb_staging --config staging.conf

# Anonymise partner data:
partners = env['res.partner'].search([('email', '!=', False)])
for p in partners:
p.write({
    'email': f'user_{p.id}@staging.example.com',
    'phone': '555-0000',
    'mobile': False,
})
env.cr.commit()
exit()

Also update mail server settings to prevent staging from sending real emails:

# In staging.conf:
smtp_server = localhost   # point to a fake SMTP like MailHog

Step 5 — Update System Parameters for Staging

After cloning production, these settings must be updated for staging:

-- In psql connected to mydb_staging:
UPDATE ir_config_parameter SET value = 'https://staging.odoo.example.com' WHERE key = 'web.base.url';
UPDATE ir_config_parameter SET value = '' WHERE key = 'web.base.url.freeze';
-- Disable any payment providers:
UPDATE payment_provider SET state = 'disabled' WHERE state = 'enabled';

Step 6 — Regular Sync Workflow

Staging drifts from production over time. Establish a sync schedule:

#!/bin/bash
# sync-staging.sh — run weekly or before major testing
set -e

# Stop staging Odoo (keep DB accessible)
docker compose -f docker-compose.staging.yml stop odoo_staging

# Drop and recreate staging DB
psql -U odoo -c "DROP DATABASE IF EXISTS mydb_staging;"
pg_dump -U odoo -Fc mydb_prod | pg_restore -U odoo -d mydb_staging --create

# Re-run anonymisation and parameter updates (script them)
./anonymise-staging.sh

# Start staging Odoo
docker compose -f docker-compose.staging.yml start odoo_staging
echo "Staging synced from production at $(date)"

How DeployMonkey Handles Staging

DeployMonkey's control panel includes a one-click "Clone to Staging" feature. It duplicates your production instance (including the database and filestore), anonymises partner data, disables payment providers, and configures a separate subdomain — all automatically. No shell access required. See Backup Odoo Database for the underlying backup workflow.

Start free at deploymonkey.app.

Frequently Asked Questions

Can I use staging as a demo environment for new users?

Yes — but refresh it from production first and ensure all sensitive data is anonymised. Keep access restricted to internal users or use HTTP basic auth at the nginx level.

How often should I sync staging from production?

Before any significant testing session. Weekly is a good default — monthly is too infrequent to catch issues that arise from production data patterns.

Does staging need the same amount of server resources as production?

Not necessarily — staging usually has far fewer concurrent users. 2 workers and 4 GB RAM is sufficient for most staging environments.

How do I prevent staging from accidentally emailing customers?

Set smtp_server in staging.conf to a fake SMTP (MailHog, Mailpit) or a catch-all address. Also disable or override all outgoing mail server records in the staging database.