跳至主要内容

Odoo CI/CD 与 GitHub Actions:测试和部署

使用 GitHub Actions 构建生产 Odoo CI/CD 管道:linting、runbot 式测试、多版本矩阵、分阶段部署、零停机生产。

E
ECOSIRE Research and Development Team
|2026年5月4日更新于 2026年10月4日9 分钟阅读1.7k 字数|

本文目前仅提供英文版本。翻译即将推出。

Most Odoo deployments still ship via "SSH in, git pull, restart Odoo." That works until it doesn't — you push a syntax error, the service crashes, and you're explaining to leadership why customers can't log in.

A real CI/CD pipeline catches errors before they reach production and adds the safety nets (staging tests, automatic backups, health checks) that turn deploys into routine non-events.

This article walks through a production-grade Odoo CI/CD setup using GitHub Actions, modeled on Odoo's own runbot but tuned for SMB-to-mid-market deployments.

Key Takeaways

  • Pipeline stages: lint → unit tests → integration tests → staging deploy → smoke test → production deploy
  • Runbot-style: spin up a fresh Odoo + PG container per build; run all tests against it
  • Multi-version matrix: test against Odoo 17, 18, 19 if your modules support multiple versions
  • Pre-deploy health check refuses to deploy if last CI run failed
  • Staging cutover before production; auto-rollback on failed smoke test
  • Backup database before every production deploy; verify after
  • Maintenance mode flag flips automatically; never leave it on

Pipeline stages

A complete pipeline has these stages, in order:

1. Lint & format check   (~30 sec)
2. Unit tests           (~2 min)
3. Integration tests    (~5 min, runbot-style)
4. Build artifacts      (~1 min)
5. Deploy to staging    (~3 min)
6. Smoke test staging   (~2 min)
7. Deploy to production (~5 min, with maintenance window)
8. Health check prod    (~1 min)
9. Notify success/fail  (instant)

Total: ~20 minutes for a clean pipeline. Failures short-circuit early.

Stage 1: Lint and format check

Catch obvious errors before spinning up Postgres:

name: Odoo CI

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

jobs:
  lint:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      
      - name: Install lint tools
        run: |
          pip install pylint pylint-odoo flake8 black isort
      
      - name: Run pylint-odoo
        run: |
          pylint --load-plugins=pylint_odoo \
                 -d all -e odoolint addons/
      
      - name: Run flake8
        run: flake8 addons/ --max-line-length=120
      
      - name: Check black formatting
        run: black --check addons/
      
      - name: Check import sorting
        run: isort --check-only addons/

pylint-odoo catches Odoo-specific anti-patterns: missing manifests, wrong _name, unsafe sql.raw(), etc.

Stage 2: Unit tests

For pure utility functions, run plain pytest:

  unit_tests:
    runs-on: ubuntu-22.04
    needs: lint
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      
      - name: Install
        run: pip install -r requirements-test.txt
      
      - name: Run pytest
        run: pytest addons/*/tests/unit/ -v

Stage 3: Integration tests (runbot-style)

The expensive but high-value stage. Spin up a fresh Odoo + PG, install your modules, run TransactionCase tests:

  integration_tests:
    runs-on: ubuntu-22.04
    needs: unit_tests
    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_USER: odoo
          POSTGRES_PASSWORD: odoo
          POSTGRES_DB: odoo_test
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
    
    strategy:
      matrix:
        odoo_version: ["17.0", "18.0", "19.0"]
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      
      - name: Install Odoo ${{ matrix.odoo_version }}
        run: |
          git clone --depth=1 -b ${{ matrix.odoo_version }} \
                    https://github.com/odoo/odoo /opt/odoo
          pip install -r /opt/odoo/requirements.txt
      
      - name: Install our modules
        run: |
          ln -s ${GITHUB_WORKSPACE}/addons/* /opt/odoo/addons/
      
      - name: Run Odoo tests
        run: |
          /opt/odoo/odoo-bin \
              -d odoo_test \
              --db_host=localhost \
              --db_user=odoo \
              --db_password=odoo \
              -i my_module \
              --test-enable \
              --test-tags=my_module,post_install \
              --stop-after-init \
              --log-level=warn

The matrix lets you validate against multiple Odoo versions in parallel. Most modules pin to one version; if yours supports multiple, the matrix is gold.

Stage 4: Build artifacts

If your deployment needs pre-built artifacts (compiled assets, packaged module zip):

  build:
    runs-on: ubuntu-22.04
    needs: integration_tests
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      
      - name: Build module zip
        run: |
          for module in addons/*/; do
            module_name=$(basename "$module")
            zip -r "${module_name}.zip" "$module"
          done
      
      - uses: actions/upload-artifact@v4
        with:
          name: modules
          path: "*.zip"

Stage 5: Deploy to staging

  deploy_staging:
    runs-on: ubuntu-22.04
    needs: build
    if: github.ref == 'refs/heads/main'
    environment: staging
    steps:
      - name: SSH deploy
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: ${{ secrets.STAGING_USER }}
          key: ${{ secrets.STAGING_SSH_KEY }}
          script: |
            cd /opt/odoo/custom-addons
            git pull origin main
            sudo systemctl restart odoo
            # Update modules
            sudo -u odoo /opt/odoo/odoo-bin -d staging \
                 -u $(ls -d */ | tr -d '/' | tr '\n' ',') \
                 --stop-after-init
            sudo systemctl restart odoo

Stage 6: Smoke test staging

After staging deploy, hit a couple of URLs to confirm Odoo is up:

  smoke_test_staging:
    runs-on: ubuntu-22.04
    needs: deploy_staging
    steps:
      - name: Check Odoo login page
        run: |
          curl -fsSL https://staging.example.com/web/login | grep -q "Odoo"
      
      - name: Check API endpoint
        run: |
          curl -fsSL https://staging.example.com/web/database/list | grep -q "result"
      
      - name: Check custom endpoint
        run: |
          curl -fsSL https://staging.example.com/my_module/api/health | grep -q "ok"

If smoke fails, deploy stops. Production never gets touched.

Stage 7: Deploy to production

The critical stage. Includes maintenance mode, backup, deploy, verification:

  deploy_production:
    runs-on: ubuntu-22.04
    needs: smoke_test_staging
    environment: production
    if: github.ref == 'refs/heads/main'
    steps:
      - name: SSH production deploy
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.PROD_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            set -e
            
            # 1. Enable maintenance mode (Nginx serves branded page)
            sudo touch /opt/odoo/maintenance.flag
            sudo nginx -s reload
            
            # 2. Backup database
            BACKUP_FILE=/var/backups/prod-$(date +%Y%m%d-%H%M%S).dump
            sudo -u odoo pg_dump -Fc -d production > $BACKUP_FILE
            
            # 3. Pull and update
            cd /opt/odoo/custom-addons
            git fetch origin
            git checkout ${{ github.sha }}
            
            # 4. Update Odoo
            sudo systemctl stop odoo
            sudo -u odoo /opt/odoo/odoo-bin -d production \
                 -u $(ls -d */ | tr -d '/' | tr '\n' ',') \
                 --stop-after-init
            sudo systemctl start odoo
            
            # 5. Wait for startup
            for i in {1..60}; do
              if curl -fsS http://localhost:8069/web/login > /dev/null; then
                break
              fi
              sleep 1
            done
            
            # 6. Disable maintenance
            sudo rm /opt/odoo/maintenance.flag
            sudo nginx -s reload
            
            # 7. Verify
            curl -fsSL https://example.com/web/login | grep -q "Odoo" || \
              (sudo touch /opt/odoo/maintenance.flag && \
               sudo nginx -s reload && \
               echo "DEPLOY FAILED, MAINTENANCE RE-ENABLED" && \
               exit 1)

Critical safety: always re-enable maintenance flag if deploy fails mid-way. The script-deploy from Windows over SSH is notorious for aborting silently and leaving the flag stuck on (we hit this in production — see our deployment notes).

Stage 8: Health check production

Final validation:

  health_check:
    runs-on: ubuntu-22.04
    needs: deploy_production
    steps:
      - name: HTTP health
        run: |
          curl -fsSL https://example.com/web/login
          curl -fsSL https://example.com/api/health
      
      - name: Database health (via web/database/list)
        run: |
          curl -fsSL https://example.com/web/database/list

Stage 9: Notify

  notify:
    runs-on: ubuntu-22.04
    needs: health_check
    if: always()
    steps:
      - name: Slack notification
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_DEPLOY_WEBHOOK }}
          SLACK_TITLE: "Odoo Deploy ${{ needs.deploy_production.result }}"
          SLACK_MESSAGE: "Commit: ${{ github.sha }}"

Pipeline performance comparison

A complete pipeline timing comparison:

StageConservative (sequential)Optimized (parallel matrix)
Lint30s30s
Unit tests2m2m
Integration tests15m (3 versions sequential)6m (3 versions parallel)
Build1m1m
Staging deploy3m3m
Smoke staging2m2m
Prod deploy5m5m
Prod health1m1m
Total~30m~20m

For high-frequency deployments (multiple per day), the parallel matrix matters. For weekly deployments, sequential is fine and simpler.

Pre-deploy gates

Before any deploy, gate on:

  • All required tests passed
  • Latest CI run on the branch concluded with success
  • No pending merge conflicts
  • Environment-specific approval (production typically requires manual approval after staging)

GitHub Actions environments support manual approval gates — use them for production.

Rollback strategy

If the deploy fails post-cutover:

# 1. Re-enable maintenance
sudo touch /opt/odoo/maintenance.flag && sudo nginx -s reload

# 2. Restore from backup
sudo systemctl stop odoo
sudo -u postgres dropdb production
sudo -u postgres createdb -O odoo production
sudo -u odoo pg_restore -d production $LATEST_BACKUP

# 3. Revert code
cd /opt/odoo/custom-addons
git checkout $PREV_COMMIT_SHA

# 4. Restart
sudo systemctl start odoo

# 5. Disable maintenance
sudo rm /opt/odoo/maintenance.flag && sudo nginx -s reload

Practice this. If you've never tested rollback, you don't have rollback.

Frequently Asked Questions

Should I run integration tests on every push or only on PR?

Run lint + unit on every push (cheap). Run integration on PRs to main (expensive). For trunk-based workflows, run integration on every push to main and on PRs targeting main. Don't run integration on every feature-branch push — too expensive.

What about runbot-style ephemeral environments?

Some teams spin up a fresh Odoo + DB per PR for QA. Heavy but useful for high-traffic projects. GitHub Actions can do this via dedicated runners or by hitting an external orchestrator (Kubernetes, Docker Swarm). For SMBs, a single staging environment with PR-based promote-to-staging is more cost-effective.

How do I handle database migrations in CI?

Two options: (1) Migrations run automatically when you -u my_module (Odoo's openupgrade hooks fire). (2) Custom Python scripts run pre-Odoo-restart. For destructive migrations (rename column, drop table), back up first and test on a staging copy of prod.

Can I deploy without downtime?

Mostly yes. Database schema changes are the bottleneck — for additive changes (new column with default), Odoo 19 can apply them without locking. For destructive changes (rename, drop), you need maintenance mode for the schema-update window. Plan additive-then-cleanup migrations to minimize downtime.

What's the best practice for secrets in GitHub Actions?

GitHub Secrets for: SSH keys, database passwords, API keys, webhook URLs. Environments for production approvals. Never echo secrets to logs (Actions auto-redacts but it's still risky). Rotate quarterly.


CI/CD turns Odoo deploys from anxiety-inducing into routine. ECOSIRE's Odoo support and maintenance service sets up CI/CD pipelines and handles the runbook for production deployments. See our Odoo customization service for module development with full CI integration, or browse our Odoo modules catalog for production-grade modules built on this exact pipeline. If you want a developer who works this way inside your own team, see hiring an Odoo developer.

E

作者

ECOSIRE Research and Development Team

技术写作

ECOSIRE 技术写作团队的内容涵盖 Odoo ERP、Shopify 电子商务、AI 智能体、Power BI 分析、GoHighLevel 自动化以及企业软件最佳实践。我们的指南帮助企业做出明智的技术决策。

ECOSIRE

使用 Odoo ERP 转变您的业务

专家 Odoo 实施、定制和支持可简化您的运营。