Skip to main content
Odoo.sh · For partners

Odoo.sh for partners: dev, staging and production without breaking anything for the customer

A partner's real Odoo.sh workflow: branches per task and version, the build as a gate, what the staging copy solves and why an upgrade ignores part of your XML.

Odoo dashboard on a real backend, where every build that passes the gate ends up

The rule that avoids most Odoo.sh scares fits in one sentence: nothing reaches production without a green build on a real copy of the customer's database, with the same commit that is about to be merged. The documentation explains the platform, not a partner's workflow. Here is ours.

This piece expands and replaces Odoo.sh for ecommerce; the shop summary is at the end.

Three stages, and what each one actually does

Odoo.sh has three stages: production (a single branch), staging and development. The difference is which database each build starts from:

  • Development: a fresh database with demo data, installs the branch's modules and runs the tests; it lasts about three days. It is the only stage that tests: tests rely on demo data.
  • Staging: the build duplicates the production database and neutralizes it. The copy is deleted after a month.
  • Production: the push restarts the server; if you bumped the module's version in __manifest__.py, it runs the update (equivalent to -u) and the instance goes under maintenance. If it fails, it reverts to the previous revision and restores the database.

Hence the consequence almost nobody reads: merging carries the code, never what you changed by hand in the staging database. Whatever you want to arrive goes in the module's XML, with the version bumped.

Branch structure for a partner

With a single customer, dev, staging and production are enough. With several: one development branch per task; one staging branch per customer and delivery, rebuilt before each validation; the single production branch Odoo.sh enforces; and, if the module goes to the App Store, one per Odoo version.

Our repository has 144 branches today, among them 17.0, 18.0, 19.0 and production: 41 carry a -17, -18 or -19 suffix (one connector, one version) and 19 are production-pre-<change>-<date>: the commit production was running before each delicate deployment. Rolling back is a push.

Verified trap: a branch created with git builds on the project's default version, not on the version of the branch you fork. We forked a -17 from 17.0 and Odoo.sh built it as 19.0: the 17.0.x manifest ended up uninstallable. The version is set in the branch's settings, and only on development branches.

The build as a gate: what it checks and what not

A build is green (no errors or warnings), yellow (warnings) or red (errors); only green ships. A development one checks that the branch's modules install on a clean database with demo data and that their tests pass. What it does not check:

  • Modules outside the branch. By default it installs only yours, without submodules. The settings offer the full installation —submodules and every standard module, but no tests— or a comma-separated list of technical names: that is the useful one.
  • The upgrade. It installs from scratch; it never runs -u on a database with history.
  • Real data. That is staging, which runs no tests.

That is why the gate has two leaves: the development build runs tests that install, not just unit tests; and the staging branch runs the real upgrade on the production copy, reading update.log to the end. Look for the line 0 failed, 0 error(s) of N tests with N greater than zero: a green of zero tests is an empty green. More causes, in an Odoo 19 module compiles but does not install.

Staging with data: what the copy solves and what it does not

Neutralization solves the dangerous part: no emails go out (they show up in the Mails tab), scheduled actions do not run, IAP is off, carriers and payment providers run in test mode, and neutralize.log says what was switched off. What it does not solve:

  • Anonymizing is still on you. It switches processes off, it does not delete personal data. If the team must not see customer data: Developer reaches neither staging nor production, Tester reaches staging, only Admin production.
  • External API credentials travel with the copy. A connector on staging with production keys talks to Amazon or Mirakl as if it were production: enable its sandbox mode or clear the keys.
  • The staging database does not refresh itself. By default the branch updates the previous build and the database survives between pushes, with its data already touched; with New build, every push starts from a fresh copy. Crons are triggered by hand, and manual backups last three days, capped at five a day.

SSH and psql on production: what to touch and what not

Odoo.sh gives you a shell on production. We go in to read (psql, lnav ~/logs/odoo.log), to diagnose with odoo-bin shell and to run a module's suite in transactional mode. What we never do: a manual UPDATE or installing modules around the build. Details that cost an afternoon: scp and sftp are blocked, the Postgres role cannot create databases and your own modules live under ~/src/user. One relief: odoo-bin shell rolls back on exit on 17, 18 and 19 (odoo/cli/shell.py).

Upgrading production: noupdate, base.group_system and migrations

An upgrade is triggered by bumping the version in the manifest; Odoo.sh takes a backup flagged Update —also when requirements.txt changes— and runs the -u. This is where the most expensive trap we have hit lives. The usual idiom for making the administrator inherit your group is:

<record id="base.group_system" model="res.groups">
    <field name="implied_ids" eval="[(4, ref('group_mi_manager'))]"/>
</record>

It works on a fresh install and is silently ignored on an upgrade. The reason lives in the customer's database: noupdate sits on the ir_model_data row. On an update, the loader only rewrites the record if not (update and d_noupdate) (odoo/orm/models.py on 19, odoo/models.py on 17 and 18), and the upsert that maintains that table refreshes model and res_id but never the noupdate column: once set, it is never cleared. And you cannot work it out from the addon: on all three versions the base.group_system record sits in a <data> with no noupdate and can still arrive flagged:

select name, noupdate from ir_model_data
 where module='base' and name in ('group_system','group_user');

On our Odoo.sh production it returns t for both; across the 69 databases our test rig has built from scratch, group_user comes back t and group_system comes back f. Same XML, two outcomes.

It happened to us on our Mirakl connector 16.15.0: the credentials moved behind a "Mirakl Manager" group. On fresh installs the administrator inherited it; on upgraded ones it had no members and nobody could touch the keys. The tests were green because a clean install does apply the record. The fix, in 16.15.1: keep the XML and add migrations/19.0.16.15.1/post-migrate.py, which writes the implication in SQL over res_groups_implied_rel and res_groups_users_rel with ON CONFLICT DO NOTHING, plus a test that fails if the group ends up empty.

Migrations: a migrations/<version>/ folder with pre-*.py, post-*.py and end-*.py files, each with migrate(cr, version), where version is the one previously installed. Odoo only runs them when the module is flagged for upgrade: on a fresh install none runs, which is why the XML is needed. If the major version changes, the process differs: Odoo 19 migration errors and the go-live checklist.

Traps the documentation does not mention

  • Builds that fall asleep. An idle development build hibernates and its crons do not run. If you use it as a test bench, keep it awake: we run a launchd agent that every 240 seconds does curl /web/health and ssh -n … true against each build.
  • The build you validate may be the old one. The identifier changes with every push and the previous one lingers: two coexist over SSH. Before calling anything good, grep for what you just changed in ~/src/user.
  • The manifest series fails differently each way. On 19, an 18.0.x or 17.0.x manifest does not blow up: the server warns has an incompatible version, setting installable=False. The other way round, on 17 or 18, a 19.0.x.y.z stops parsing with invalid manifest.

Delivery checklist

  1. Green build (not yellow) on development, tests that install, N greater than zero.
  2. Staging rebuilt today from production; external credentials in sandbox mode or cleared.
  3. Version bumped in the manifest and update.log read to the end, migrations included.
  4. Customer validation on staging, crons triggered by hand, emails reviewed in Mails.
  5. A production-pre-<change>-<date> branch from the commit production is running, and a manual backup if it touches data.
  6. Merge outside peak hours and then check odoo.log, the permission group and the crons.

We offer this workflow as Odoo engineering for consultancies and use it on every implementation and Odoo 19 migration. If you are a partner or a software house, the technology partner programme explains how we work.

If what you have is an ecommerce

Touching a marketplace connector or a stock rule in production is gambling with real orders: duplicate labels, orders imported twice, half-finished syncs. New modules, stock changes and connector updates (Amazon, Mirakl, ManoMano, SEUR, MRW, Correos Express) go through staging, where carriers and payment providers run in test mode: the place to test a label without a van leaving.

Frequently asked questions

Does Odoo.sh run tests on staging or production?

No. Only on development builds, on a fresh database with demo data. Staging validates with real data and no tests; production loads no demo and runs no tests.

Does a green build guarantee the module installs on the customer's Odoo?

It guarantees it installs on a clean database of that version. It does not guarantee the upgrade on a database with history, nor coexistence with modules outside the branch: that is covered on the customer's staging.

Useful links inside FlexigoTech

Technology partner programmeHow we work with Odoo partners and software housesOdoo engineering for consultanciesTechnical team with a test bench on 19, 18 and 17An Odoo 19 module compiles but does not installThe API traps that only show up on a real buildGreen tests and the connector fails in productionWhy the test that counts is the real install

What we do about this

Odoo connector for your softwareWhat it does, screenshots, versions and price.

Delivering projects on Odoo.sh and want a test bench that does not depend on luck?

We set up the branch workflow, the build as a gate and the migrations for your customers, or review the one you already have. Email comercial@flexigobe.com or call +34 616 809 504.

Talk to an engineer