Development
Migrations
How odoo db migrate decides which modules to update, the snapshot and rollback around it, and the marker that makes a migrated build a one-query no-op.
On this page
Odoo only creates columns, reloads views and data and runs migration scripts for modules being installed or updated. Changed code without -u leaves the schema silently behind it. odoo db migrate updates what changed, not everything, and it is the same command in the dev shell, on a server and in a container.
odoo db migrate # the default database
odoo db migrate acme --rollback # roll back to the snapshot if it fails
odoo db migrate --all --full # every database matching dbfilter, -u allWhat gets updated
Two signals put a module in the update set:
Its content checksum differs from the one stored the last time a migration of this database succeeded.
Version drift:
ir_module_module.latest_versionis behind the manifest on disk, as in a database restored from somewhere its modules were never updated.
The checksums use OCA module_auto_update's algorithm and storage (module_auto_update.installed_checksums, honouring module_auto_update.exclude_patterns), so that module, click-odoo-update and this command agree on what changed and can share a database. A database with no stored checksums is updated in full once, as a baseline; odoo db provision records the baseline for a fresh install. The plan is printed before anything runs:
3 module(s) to update: queue_job (18.0.3.4.1 → 18.0.4.0.0), sale_x (code changed), …--full updates every installed module instead, and --also <M,…> adds modules even when unchanged.
The build marker
A build assembled in the Nix store also records which build migrated the database (odoo_nix.migrated_build). With --if-needed, a database that this build already migrated costs one query and nothing else. That is the path every start of services.odoo-nix takes. The marker lives in the database, not on disk, so restoring a dump from elsewhere is itself what triggers the next migration.
The safety net
A lock. An advisory lock, so a manual migration and a deploy cannot run at once.
A verified snapshot. Before anything changes, the database is snapshotted and the snapshot verified readable (
pg_restore --listfordump; every member's CRC and itsdump.sqlforzip). One that does not verify is removed and nothing is migrated.--snapshot zip(the default) includes the filestore,dumpis the database alone, andnone(or--no-backup) skips it.Snapshot files. They are written as
premigrate-<DB>-YYYYmmdd-HHMMSS.dump[.zip]in<data_dir>/backups/<DB>/premigrate/, 0600 from the first byte in a 0700 directory.--keep Nprunes only these, never a backup somebody took on purpose, and never after a failure.Rollback.
--rollbackdrops the database, recreates it with the same encoding and collation, and loads the snapshot. The filestore is left alone: attachments are content-addressed and only ever added. That is also why rollback does not use Odoo's ownexp_drop(it deletes the filestore) orrestore_db(it loads the new code against the restored old schema).An uninitialised database is skipped with exit 0, or provisioned with
--provision-if-empty.
The snapshot steps are python-devops-nix's migrate flow, the same one frappe-nix runs. Their messages are odoo-migrate(<DB>): … lines, at their syslog priority under systemd, so a failed migration is visible in journalctl -p err.
On a server, odoo-migrate.service runs this before every start of Odoo: see Migrations on deploy.