Skip to main content
Technical Guide

Odoo CI/CD and Staging: Complete Guide

Master Odoo development workflows with staging environments, CI/CD pipelines, and automated testing. Learn Git branching strategies, GitHub Actions, GitLab CI, and deployment best practices.

18-22 min read
Updated December 2024
DevOps Best Practices

Why Development Workflows Matter for Odoo

Professional Odoo development requires more than just writing code. Without proper development workflows, teams face broken deployments, lost work, and frustrated users. A well-designed CI/CD pipeline with staging environments transforms chaotic development into reliable, repeatable releases. Whether you're deploying Odoo for the first time or scaling an existing system, proper DevOps practices are essential for development teams.

Reduced Risk

Test changes in staging before production. Catch bugs before they affect real users and business operations.

Team Collaboration

Multiple developers can work simultaneously without conflicts. Code reviews ensure quality before merging.

Fast Rollbacks

When issues occur, quickly revert to the previous working version. Minimize downtime and user impact.

Odoo Development Workflow Overview


+-------------+     +-------------+     +-------------+     +-------------+
|   LOCAL     |     |   FEATURE   |     |   STAGING   |     | PRODUCTION  |
|     DEV     | --> |   BRANCH    | --> | ENVIRONMENT | --> | ENVIRONMENT |
+-------------+     +-------------+     +-------------+     +-------------+
      |                   |                   |                   |
      v                   v                   v                   v
  Developer           Git Push            Auto Deploy         Manual Deploy
  writes code         triggers CI         to staging          after approval
                          |                   |                   |
                          v                   v                   v
                     Run Tests          QA Testing          Live Users
                     Lint Code          Client Review       Business Data
                     Build Check        Integration Test

                              CI/CD PIPELINE FLOW
+-----------------------------------------------------------------------+
|  Commit --> Lint --> Unit Tests --> Build --> Deploy Staging --> QA   |
|                                                        |              |
|                                      Approval --> Deploy Production   |
+-----------------------------------------------------------------------+

Development Environment Setup

A consistent local development environment is the foundation of a good workflow. Every developer should have the same setup to avoid environment-specific bugs.

Docker for Local Development

Docker provides the most consistent development experience. Every developer gets the same Odoo version, Python version, and dependencies. For production-ready Docker configurations, see our complete Odoo Docker deployment guide.

docker-compose.dev.ymlyaml
version: '3.8'

services:
  odoo:
    image: odoo:17.0
    container_name: odoo-dev
    depends_on:
      - db
    ports:
      - "8069:8069"
      - "8072:8072"
    volumes:
      # Mount your custom addons for live development
      - ./addons:/mnt/extra-addons
      # Mount config file
      - ./config/odoo.dev.conf:/etc/odoo/odoo.conf
      # Persist filestore for attachments
      - odoo-dev-data:/var/lib/odoo
    environment:
      - HOST=db
      - USER=odoo
      - PASSWORD=odoo
    # Enable debug mode for development
    command: ["--dev=reload,qweb,werkzeug,xml"]

  db:
    image: postgres:15
    container_name: odoo-dev-db
    environment:
      - POSTGRES_USER=odoo
      - POSTGRES_PASSWORD=odoo
      - POSTGRES_DB=postgres
    ports:
      - "5432:5432"
    volumes:
      - postgres-dev-data:/var/lib/postgresql/data

volumes:
  odoo-dev-data:
  postgres-dev-data:

VS Code Configuration

Configure VS Code for optimal Odoo development with Python extensions and debugging support.

.vscode/settings.jsonjson
{
  "python.defaultInterpreterPath": "/usr/bin/python3",
  "python.linting.enabled": true,
  "python.linting.pylintEnabled": true,
  "python.linting.pylintArgs": [
    "--load-plugins=pylint_odoo",
    "--rcfile=.pylintrc"
  ],
  "python.formatting.provider": "black",
  "python.formatting.blackArgs": ["--line-length", "120"],
  "editor.formatOnSave": true,
  "files.associations": {
    "*.xml": "xml"
  },
  "[xml]": {
    "editor.defaultFormatter": "redhat.vscode-xml"
  },
  "xml.validation.enabled": true,
  "python.analysis.extraPaths": [
    "/opt/odoo/odoo",
    "/opt/odoo/addons"
  ]
}
.vscode/launch.jsonjson
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Odoo: Debug",
      "type": "python",
      "request": "launch",
      "program": "/opt/odoo/odoo-bin",
      "args": [
        "-c", "/etc/odoo/odoo.conf",
        "-d", "odoo_dev",
        "--dev=reload,qweb,werkzeug,xml"
      ],
      "console": "integratedTerminal",
      "justMyCode": false
    },
    {
      "name": "Odoo: Run Tests",
      "type": "python",
      "request": "launch",
      "program": "/opt/odoo/odoo-bin",
      "args": [
        "-c", "/etc/odoo/odoo.conf",
        "-d", "odoo_test",
        "--test-enable",
        "--stop-after-init",
        "-i", "your_module"
      ],
      "console": "integratedTerminal"
    }
  ]
}

PyCharm Configuration

For PyCharm Professional users, configure the Odoo run configuration:

1. Set Python Interpreter

Settings > Project > Python Interpreter > Add the Docker or local Python environment with Odoo dependencies.

2. Add Odoo Source Roots

Mark the odoo/ and addons/ directories as Source Roots for proper code navigation and autocomplete.

3. Configure Run Configuration

Script: odoo-bin, Parameters: -c odoo.conf -d dev_db --dev=reload,qweb,werkzeug,xml

Git Branching Strategy for Odoo

A clear branching strategy prevents merge conflicts and ensures code quality. This modified Git Flow approach works well for Odoo projects of all sizes.

Branch Structure


main/master (production)
    |
    +-- develop (staging/integration)
           |
           +-- feature/inventory-widget
           |
           +-- feature/custom-reports
           |
           +-- bugfix/invoice-calculation
           |
           +-- release/v2.1.0

Branch Naming Conventions:
---------------------------
feature/module-name-description   New features or modules
bugfix/issue-number-description   Bug fixes
hotfix/critical-issue             Production emergency fixes
release/vX.Y.Z                    Release preparation branches

main / master

Always reflects production. Only merge from release branches or hotfixes. Protected with required reviews.

  • - Direct commits blocked
  • - Requires PR approval
  • - Triggers production deploy

develop

Integration branch for staging. Features merge here first for testing before production release.

  • - Auto-deploys to staging
  • - Requires PR approval
  • - Integration testing happens here

feature/*

Short-lived branches for new development. Created from develop, merged back via PR.

  • - One feature per branch
  • - Delete after merge
  • - Run CI on every push

release/*

Preparation for production release. Final testing, version bumps, and documentation updates.

  • - Created from develop
  • - Merged to both main and develop
  • - Tag with version number

Branch Protection Rules

.github/branch-protection.mdyaml
# GitHub Branch Protection Settings

## main branch:
- Require pull request reviews before merging: Yes
- Required approving reviews: 2
- Dismiss stale pull request approvals: Yes
- Require status checks to pass: Yes
  - Required checks: lint, test, build
- Require branches to be up to date: Yes
- Include administrators: Yes
- Restrict who can push: Release managers only

## develop branch:
- Require pull request reviews before merging: Yes
- Required approving reviews: 1
- Require status checks to pass: Yes
  - Required checks: lint, test
- Allow force pushes: No
- Allow deletions: No

Branch Naming Best Practices

  • - Use lowercase with hyphens: feature/sales-report-export
  • - Include ticket number when applicable: bugfix/OD-123-fix-invoice
  • - Keep names short but descriptive
  • - Delete branches after merging to keep repo clean

Staging Environment Setup

A staging environment mirrors production, allowing you to test changes with realistic data and configuration before going live. This catches issues that only appear in production-like conditions.

Staging Requirements

Separate Server/Container

Isolated from production infrastructure

Own PostgreSQL Database

Never share database with production

Same Odoo Version

Match production Odoo and Python versions exactly

Dedicated URL

staging.yourcompany.com or similar

Test Data

Sanitized copy of production data

Email Disabled/Redirected

Prevent accidental emails to real customers

Staging Configuration

config/odoo.staging.confini
[options]
; Staging Environment Configuration
admin_passwd = $STAGING_ADMIN_PASSWORD
db_host = staging-db.yourcompany.internal
db_port = 5432
db_user = odoo_staging
db_password = False  ; Use environment variable
db_name = odoo_staging

; Paths
addons_path = /opt/odoo/odoo/addons,/opt/odoo/custom-addons
data_dir = /var/lib/odoo-staging

; Server settings
http_port = 8069
longpolling_port = 8072
proxy_mode = True

; Workers (smaller than production)
workers = 2
max_cron_threads = 1

; Logging
logfile = /var/log/odoo/staging.log
log_level = debug
log_handler = :DEBUG

; Email - CRITICAL: Disable outgoing emails
smtp_server = False
; Or redirect all emails to test mailbox:
; smtp_server = smtp.mailtrap.io
; smtp_port = 2525
; smtp_user = your_mailtrap_user
; smtp_password = your_mailtrap_password

; Development/Debug features (optional)
dev_mode = reload,qweb

Database Cloning Script

Periodically refresh staging with production data. Always sanitize sensitive information.

scripts/clone-prod-to-staging.shbash
#!/bin/bash
#
# Clone production database to staging with data sanitization
#
set -e

# Configuration
PROD_DB_HOST="prod-db.yourcompany.internal"
STAGING_DB_HOST="staging-db.yourcompany.internal"
DB_USER="odoo"
PROD_DB="odoo_production"
STAGING_DB="odoo_staging"

echo "Starting production to staging clone..."

# Stop staging Odoo
echo "Stopping staging Odoo service..."
sudo systemctl stop odoo-staging

# Create backup of production
echo "Dumping production database..."
pg_dump -h $PROD_DB_HOST -U $DB_USER -Fc $PROD_DB > /tmp/prod_backup.dump

# Drop and recreate staging database
echo "Recreating staging database..."
PGPASSWORD=$STAGING_DB_PASSWORD psql -h $STAGING_DB_HOST -U $DB_USER -c "DROP DATABASE IF EXISTS $STAGING_DB;"
PGPASSWORD=$STAGING_DB_PASSWORD psql -h $STAGING_DB_HOST -U $DB_USER -c "CREATE DATABASE $STAGING_DB OWNER $DB_USER;"

# Restore to staging
echo "Restoring to staging..."
pg_restore -h $STAGING_DB_HOST -U $DB_USER -d $STAGING_DB /tmp/prod_backup.dump

# Sanitize sensitive data
echo "Sanitizing data..."
PGPASSWORD=$STAGING_DB_PASSWORD psql -h $STAGING_DB_HOST -U $DB_USER -d $STAGING_DB << 'EOF'
-- Reset all user passwords to 'staging123'
UPDATE res_users SET password = 'staging123' WHERE id > 1;

-- Anonymize customer emails
UPDATE res_partner
SET email = CONCAT('staging_', id, '@example.com')
WHERE email IS NOT NULL AND email != '';

-- Clear sensitive payment data
TRUNCATE payment_token CASCADE;

-- Disable all outgoing mail servers
UPDATE ir_mail_server SET active = false;

-- Update system parameters for staging
UPDATE ir_config_parameter
SET value = 'https://staging.yourcompany.com'
WHERE key = 'web.base.url';

-- Clear scheduled actions that might cause issues
UPDATE ir_cron SET active = false
WHERE id IN (
    SELECT id FROM ir_cron
    WHERE name ILIKE '%mail%' OR name ILIKE '%email%'
);

EOF

# Sync filestore (optional - can be large)
echo "Syncing filestore..."
rsync -avz --delete \
    prod-server:/var/lib/odoo/filestore/$PROD_DB/ \
    /var/lib/odoo-staging/filestore/$STAGING_DB/

# Fix permissions
sudo chown -R odoo:odoo /var/lib/odoo-staging

# Start staging Odoo
echo "Starting staging Odoo..."
sudo systemctl start odoo-staging

# Cleanup
rm /tmp/prod_backup.dump

echo "Clone complete! Staging is now a sanitized copy of production."
echo "All user passwords have been reset to 'staging123'"

Data Sanitization is Critical

Never skip data sanitization when cloning production to staging. Real customer data in staging environments can lead to GDPR violations, accidental customer communications, and security breaches. Always anonymize PII, reset passwords, and disable email sending.

URL Conventions

EnvironmentURL PatternPurpose
Productionerp.yourcompany.comLive business operations
Stagingstaging.yourcompany.comQA and client testing
Developmentdev.yourcompany.comDeveloper integration testing
Locallocalhost:8069Individual development

CI/CD Pipeline Configuration

Automate testing and deployment with CI/CD pipelines. This ensures every code change is validated before merging and deployments are consistent and repeatable.

GitHub Actions Workflow

.github/workflows/odoo-ci.ymlyaml
name: Odoo CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

env:
  ODOO_VERSION: "17.0"
  PYTHON_VERSION: "3.10"

jobs:
  lint:
    name: Code Quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}

      - name: Install linting tools
        run: |
          pip install flake8 pylint pylint-odoo black isort

      - name: Run flake8
        run: |
          flake8 addons/ --max-line-length=120 --ignore=E501,W503

      - name: Run pylint-odoo
        run: |
          pylint addons/*/ --load-plugins=pylint_odoo \
            --disable=all \
            --enable=odoolint \
            --rcfile=.pylintrc || true

      - name: Check formatting with black
        run: |
          black --check --line-length 120 addons/

  test:
    name: Unit Tests
    runs-on: ubuntu-latest
    needs: lint

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: odoo
          POSTGRES_PASSWORD: odoo
          POSTGRES_DB: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}

      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}

      - name: Install Odoo dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y wkhtmltopdf libldap2-dev libsasl2-dev
          pip install -r requirements.txt

      - name: Clone Odoo
        run: |
          git clone --depth 1 --branch ${{ env.ODOO_VERSION }} \
            https://github.com/odoo/odoo.git /tmp/odoo

      - name: Run Odoo tests
        env:
          PGHOST: localhost
          PGUSER: odoo
          PGPASSWORD: odoo
        run: |
          /tmp/odoo/odoo-bin \
            -d test_db \
            --test-enable \
            --stop-after-init \
            --log-level=test \
            -i $(ls -d addons/*/ | xargs -n1 basename | paste -sd,) \
            --addons-path=/tmp/odoo/addons,/tmp/odoo/odoo/addons,./addons

  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/develop' && github.event_name == 'push'

    steps:
      - uses: actions/checkout@v4

      - name: Deploy to staging server
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: ${{ secrets.STAGING_USER }}
          key: ${{ secrets.STAGING_SSH_KEY }}
          script: |
            cd /opt/odoo/custom-addons
            git fetch origin develop
            git checkout develop
            git pull origin develop
            sudo systemctl restart odoo-staging

      - name: Update Odoo modules
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: ${{ secrets.STAGING_USER }}
          key: ${{ secrets.STAGING_SSH_KEY }}
          script: |
            # Wait for Odoo to start
            sleep 30
            # Update all custom modules
            /opt/odoo/odoo-bin -c /etc/odoo/staging.conf \
              -d odoo_staging \
              -u all \
              --stop-after-init
            sudo systemctl restart odoo-staging

      - name: Notify on success
        uses: slackapi/[email protected]
        with:
          payload: |
            {
              "text": "Staging deployed successfully from develop branch"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment: production

    steps:
      - uses: actions/checkout@v4

      - name: Create backup before deploy
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.PROD_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            /opt/odoo/scripts/backup.sh

      - name: Deploy to production
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.PROD_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            cd /opt/odoo/custom-addons
            git fetch origin main
            git checkout main
            git pull origin main
            sudo systemctl restart odoo

      - name: Update modules
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.PROD_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            sleep 30
            /opt/odoo/odoo-bin -c /etc/odoo/odoo.conf \
              -d odoo_production \
              -u all \
              --stop-after-init
            sudo systemctl restart odoo

GitLab CI Configuration

If you're using GitLab for your repositories, GitLab CI provides powerful pipeline features. For more details on GitLab-specific Odoo workflows, see our Odoo GitLab CI/CD guide.

.gitlab-ci.ymlyaml
stages:
  - lint
  - test
  - deploy

variables:
  ODOO_VERSION: "17.0"
  PYTHON_VERSION: "3.10"
  POSTGRES_DB: test_db
  POSTGRES_USER: odoo
  POSTGRES_PASSWORD: odoo

.default_rules:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "develop"
    - if: $CI_COMMIT_BRANCH == "main"

lint:
  stage: lint
  image: python:3.10
  extends: .default_rules
  before_script:
    - pip install flake8 pylint pylint-odoo black
  script:
    - flake8 addons/ --max-line-length=120
    - black --check --line-length 120 addons/
    - pylint addons/*/ --load-plugins=pylint_odoo --disable=all --enable=odoolint || true

test:
  stage: test
  image: python:3.10
  extends: .default_rules
  services:
    - postgres:15
  variables:
    PGHOST: postgres
    PGUSER: odoo
    PGPASSWORD: odoo
  before_script:
    - apt-get update && apt-get install -y wkhtmltopdf libldap2-dev libsasl2-dev
    - pip install -r requirements.txt
    - git clone --depth 1 --branch $ODOO_VERSION https://github.com/odoo/odoo.git /tmp/odoo
  script:
    - |
      /tmp/odoo/odoo-bin \
        -d test_db \
        --test-enable \
        --stop-after-init \
        --log-level=test \
        -i $(ls -d addons/*/ | xargs -n1 basename | paste -sd,) \
        --addons-path=/tmp/odoo/addons,/tmp/odoo/odoo/addons,./addons

deploy_staging:
  stage: deploy
  image: alpine:latest
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$STAGING_SSH_KEY" | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
  script:
    - ssh -o StrictHostKeyChecking=no $STAGING_USER@$STAGING_HOST "
        cd /opt/odoo/custom-addons &&
        git pull origin develop &&
        sudo systemctl restart odoo-staging
      "
  environment:
    name: staging
    url: https://staging.yourcompany.com

deploy_production:
  stage: deploy
  image: alpine:latest
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  when: manual
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$PROD_SSH_KEY" | ssh-add -
  script:
    - ssh -o StrictHostKeyChecking=no $PROD_USER@$PROD_HOST "
        /opt/odoo/scripts/backup.sh &&
        cd /opt/odoo/custom-addons &&
        git pull origin main &&
        sudo systemctl restart odoo
      "
  environment:
    name: production
    url: https://erp.yourcompany.com

Automated Testing Configuration

Configure Odoo test settings for consistent CI runs.

addons/your_module/tests/__init__.pypython
# Import all test classes
from . import test_models
from . import test_wizards
from . import test_reports
addons/your_module/tests/test_models.pypython
# -*- coding: utf-8 -*-
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import ValidationError


@tagged('post_install', '-at_install')
class TestYourModel(TransactionCase):
    """Test cases for your_module models."""

    @classmethod
    def setUpClass(cls):
        """Set up test fixtures."""
        super().setUpClass()
        cls.partner = cls.env['res.partner'].create({
            'name': 'Test Partner',
            'email': '[email protected]',
        })
        cls.your_model = cls.env['your.model'].create({
            'name': 'Test Record',
            'partner_id': cls.partner.id,
        })

    def test_create_record(self):
        """Test record creation."""
        record = self.env['your.model'].create({
            'name': 'New Record',
            'partner_id': self.partner.id,
        })
        self.assertTrue(record.id)
        self.assertEqual(record.name, 'New Record')

    def test_compute_field(self):
        """Test computed field calculation."""
        self.your_model.amount = 100
        self.your_model.quantity = 5
        self.assertEqual(self.your_model.total, 500)

    def test_constraint_validation(self):
        """Test model constraints raise errors."""
        with self.assertRaises(ValidationError):
            self.env['your.model'].create({
                'name': '',  # Should fail - name required
                'partner_id': self.partner.id,
            })

    def test_action_method(self):
        """Test action method behavior."""
        self.your_model.action_confirm()
        self.assertEqual(self.your_model.state, 'confirmed')

Test Tags Explained

  • post_install: Run after module installation
  • -at_install: Do not run during installation
  • standard: Include in standard test runs
  • Use custom tags like @tagged('slow') for long-running tests

Staging to Production Workflow

Moving code from staging to production requires careful planning and execution. Follow this workflow to minimize risk and ensure smooth deployments.

Pre-Deployment Checklist

1

Code Review Completed

All PRs reviewed and approved by at least one team member.

2

Tests Passing

All automated tests pass in CI pipeline.

3

Staging QA Complete

Manual testing in staging environment signed off.

4

Database Migration Tested

Module updates run successfully on staging.

5

Rollback Plan Ready

Know how to revert if issues occur.

6

Backup Created

Fresh production backup taken before deployment.

7

Stakeholders Notified

Team and users aware of deployment window.

Module Update Procedures

scripts/deploy-production.shbash
#!/bin/bash
#
# Production Deployment Script
#
set -e

echo "=== Production Deployment Starting ==="
echo "Time: $(date)"

# Configuration
ODOO_CONFIG="/etc/odoo/odoo.conf"
DB_NAME="odoo_production"
MODULES_TO_UPDATE="your_module,another_module"  # or "all" for all modules

# Step 1: Create backup
echo "Step 1: Creating backup..."
/opt/odoo/scripts/backup.sh
echo "Backup completed"

# Step 2: Pull latest code
echo "Step 2: Pulling latest code..."
cd /opt/odoo/custom-addons
git fetch origin main
git checkout main
git pull origin main
echo "Code updated to: $(git rev-parse --short HEAD)"

# Step 3: Stop Odoo gracefully
echo "Step 3: Stopping Odoo..."
sudo systemctl stop odoo
sleep 10

# Step 4: Run module updates
echo "Step 4: Updating modules..."
/opt/odoo/odoo-bin -c $ODOO_CONFIG \
    -d $DB_NAME \
    -u $MODULES_TO_UPDATE \
    --stop-after-init \
    --log-level=info

# Step 5: Start Odoo
echo "Step 5: Starting Odoo..."
sudo systemctl start odoo
sleep 30

# Step 6: Health check
echo "Step 6: Running health check..."
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8069/web/health)
if [ "$HTTP_STATUS" -eq 200 ]; then
    echo "Health check passed!"
else
    echo "WARNING: Health check returned $HTTP_STATUS"
    echo "Check logs: sudo journalctl -u odoo -f"
fi

echo "=== Deployment Complete ==="
echo "Time: $(date)"

Rollback Strategy

When deployments go wrong, quick rollback minimizes impact. Always have a tested rollback procedure.

scripts/rollback.shbash
#!/bin/bash
#
# Emergency Rollback Script
#
set -e

echo "=== EMERGENCY ROLLBACK ==="
echo "This will restore the previous deployment."
read -p "Are you sure? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
    echo "Rollback cancelled."
    exit 1
fi

# Configuration
BACKUP_DIR="/opt/odoo/backups"
ODOO_CONFIG="/etc/odoo/odoo.conf"
DB_NAME="odoo_production"

# Find most recent backup
LATEST_BACKUP=$(ls -t $BACKUP_DIR/full_backup_*.tar.gz | head -1)
echo "Using backup: $LATEST_BACKUP"

# Stop Odoo
echo "Stopping Odoo..."
sudo systemctl stop odoo

# Restore code to previous version
echo "Reverting code..."
cd /opt/odoo/custom-addons
git checkout HEAD~1

# Restore database (optional - only if DB changes need reverting)
read -p "Restore database from backup? (yes/no): " restore_db
if [ "$restore_db" == "yes" ]; then
    echo "Restoring database..."
    tar -xzf $LATEST_BACKUP -C /tmp/

    sudo -u postgres dropdb $DB_NAME
    sudo -u postgres createdb -O odoo $DB_NAME
    sudo -u postgres pg_restore -d $DB_NAME /tmp/db_*.dump

    rm /tmp/db_*.dump /tmp/filestore_*.tar.gz
fi

# Start Odoo
echo "Starting Odoo..."
sudo systemctl start odoo
sleep 30

# Verify
echo "Verifying service..."
sudo systemctl status odoo

echo "=== Rollback Complete ==="
echo "Please verify the system manually."

Deployment Best Practices

  • - Deploy during low-traffic periods
  • - Update modules one at a time for large changes
  • - Monitor logs during and after deployment
  • - Have database admin on standby for migration issues
  • - Document each deployment for future reference

Skip the Setup with OEC.sh Staging

Setting up and maintaining staging environments takes significant time and DevOps expertise. OEC.sh provides built-in staging environments with one-click creation and easy promotion to production.

One-Click Staging

Create staging environments instantly from dashboard

Database Cloning

Clone production data to staging with auto-sanitization

Git Integration

Auto-deploy from branches with webhook triggers

Easy Promotion

Promote staging to production with one click

Automatic Backups

Pre-deployment backups created automatically

Rollback Support

One-click rollback to previous version

Self-Managed Staging

  • - Hours to set up
  • - Manual server maintenance
  • - DIY database cloning
  • - Custom deployment scripts

OEC.sh Staging

  • Minutes to create
  • Fully managed
  • One-click clone
  • Git auto-deploy

Odoo.sh Staging

  • - Built-in branches
  • - Limited to Odoo.sh hosting
  • - Higher per-user pricing
  • - Single cloud provider

Frequently Asked Questions

What is a staging environment for Odoo?

A staging environment is a pre-production replica of your live Odoo system where you test code changes, module updates, and new features before deploying to production. It should mirror your production setup with its own dedicated server, PostgreSQL database, and URL (e.g., staging.yourcompany.com). Staging uses sanitized production data to simulate real-world conditions while protecting sensitive customer information. This testing ground catches bugs and compatibility issues before they affect your live business operations.

How do I set up CI/CD for Odoo?

Setting up CI/CD for Odoo involves: 1) Choose a platform (GitHub Actions or GitLab CI), 2) Configure automated tests that run on every commit (linting with pylint-odoo, unit tests with Odoo's test framework, XML validation), 3) Set up automated deployment to staging when changes merge to the develop branch, 4) Implement manual approval gates for production deployments. Use Docker containers in your CI pipeline for consistent test environments. Store credentials and SSH keys in your platform's secrets management. For detailed configurations, see our guides on Odoo Docker deployment and GitLab CI.

Should I use GitHub Actions or GitLab CI?

Both GitHub Actions and GitLab CI work excellently for Odoo. Choose GitHub Actions if your team already uses GitHub for code hosting, prefers the marketplace ecosystem of pre-built actions, and wants simple YAML configuration. Choose GitLab CI if you want an all-in-one DevOps platform, need more advanced pipeline features (parent-child pipelines, manual gates), or prefer tighter integration between code and CI/CD. For Odoo specifically, both platforms support Docker-based testing and deployment equally well. The choice often depends on your existing tooling and team preferences rather than technical limitations.

How do I clone production to staging?

To clone production to staging: 1) Stop the staging Odoo service, 2) Create a pg_dump backup of your production database, 3) Drop and recreate the staging database, 4) Restore the backup to staging, 5) Run SQL scripts to sanitize sensitive data (anonymize emails, reset passwords, disable mail servers, clear payment tokens), 6) Optionally sync the filestore with rsync, 7) Restart staging Odoo. Automate this process with a bash script that runs weekly or monthly. Never skip data sanitization - it's critical for GDPR compliance and preventing accidental customer communications from staging. For detailed scripts and best practices, see our Odoo backup and recovery guide.

What's the best workflow for Odoo development?

The best Odoo development workflow uses Git Flow with staging: 1) Developers create feature branches from develop, 2) Code changes go through automated CI tests (linting, unit tests), 3) Features merge to develop branch via pull request with peer review, 4) Develop branch auto-deploys to staging for QA testing, 5) After staging validation, create a release branch, 6) Release branch merges to main/master which triggers production deployment, 7) Tag releases with version numbers for easy rollbacks. Use Docker for consistent local development environments. This workflow separates dev/staging/production clearly, enables parallel development, and ensures code quality through automated testing and manual review gates.

How do I deploy from staging to production?

To deploy from staging to production: 1) Validate all changes thoroughly in staging with QA testing and stakeholder approval, 2) Create a backup of production database and code, 3) Merge your tested changes from develop/release branch to main branch, 4) SSH to production server or trigger automated deployment pipeline, 5) Pull latest code from main branch, 6) Restart Odoo with module update flag (-u module_name or -u all), 7) Monitor logs and verify functionality, 8) Have a rollback plan ready if issues arise. For critical updates, deploy during low-traffic windows. Consider blue-green deployments or rolling updates for zero-downtime. OEC.sh automates this entire process with one-click promotion from staging to production.

Can I use GitHub Actions with Odoo?

Yes, GitHub Actions works excellently with Odoo. You can automate linting (pylint, flake8), run Odoo unit tests, check XML syntax, validate module manifests, and deploy to staging or production. Use the official Odoo Docker images or build custom images with your dependencies. Store secrets like database credentials and SSH keys in GitHub Secrets. The workflow runs on every push to feature branches, automatically deploys to staging when merging to develop, and requires manual approval for production deployments. See our complete GitHub Actions configuration examples in this guide.

How do I run Odoo tests in CI/CD pipelines?

Odoo has a built-in test framework based on Python unittest. Run tests with: odoo-bin -d test_db --test-enable --stop-after-init -i your_module. For CI/CD, spin up a temporary PostgreSQL database service, install your module with tests enabled, and check the exit code. You can also use pytest-odoo for more advanced testing features and better reporting. In GitHub Actions or GitLab CI, use Docker services to run PostgreSQL alongside your test container. Cache pip dependencies between runs to speed up pipeline execution.

Ready to Streamline Your Odoo Workflow?

Skip the infrastructure complexity. OEC.sh provides staging environments, Git integration, and automated deployments out of the box.