Pular para o conteúdo principal

Odoo CI/CD com GitHub Actions: teste e implantação

Crie um pipeline de produção Odoo CI/CD com GitHub Actions: linting, teste estilo runbot, matriz multiversão, implantação de teste, produção com tempo de inatividade zero.

E
ECOSIRE Research and Development Team
|4 de maio de 2026Atualizado em 4 de outubro de 20269 min de leitura1.7k Palavras|

Este artigo está atualmente disponível apenas em inglês. Tradução em breve.

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.

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

Escrito por

ECOSIRE Research and Development Team

Redação técnica

A equipe de redação técnica da ECOSIRE escreve sobre Odoo ERP, e-commerce com Shopify, agentes de IA, análise de dados com Power BI, automação com GoHighLevel e boas práticas de software empresarial. Nossos guias ajudam as empresas a tomar decisões de tecnologia bem fundamentadas.

ECOSIRE

Transforme seu negócio com o Odoo ERP

Implementação, personalização e suporte especializado do Odoo para agilizar suas operações.