of release — release promotion¶
of release promotes a pinned release to an environment. A release is a
lockfile: a YAML file that pins every repository of an environment to an
exact Git ref (a tag, ideally, or a commit SHA). Promoting test → production
is "apply the same lockfile to production", not "merge a branch".
The flow mirrors what Odoo.sh does: only code travels between environments,
the database never does; a module is updated (-u) only when its manifest
version changed; a backup is taken before touching production; and every
promotion is recorded.
Lockfile¶
version: 1
project: nikro
environment: test
created_at: '2026-10-03T18:21:07'
repositories:
- url: git@github.com:trixocom/payment-siro-roela-ce.git
ref: v19.0.2.2.6 # tag or commit SHA — what gets checked out
commit: 18657fd0c2e0... # informative; verified after checkout
branch: main # informative
image: nikro_test:19.0 build=3f1c9a0b2d44
Keep one lockfile per environment in the customer's repository
(envs/stg.lock.yml, envs/test.lock.yml, envs/main.lock.yml). A
promotion is then a pull request that copies test.lock.yml to
main.lock.yml: branch protection on that repository is the approval gate,
and the PR is the record of who asked for what.
Repositories are matched by org/repo, ignoring the host, the SSH/HTTPS
spelling and per-repository SSH host aliases: git@github.com-apibank:org/repo.git,
https://github.com/org/repo and git@forgejo.example:org/repo.git are the same
repository (an environment may clone from a mirror; the commits are the same).
Commands¶
of release lock <env> [-o FILE] [--no-tags]¶
Snapshot the refs currently checked out in <env>/addons/*. When a tag
points at HEAD it is used as ref (semantic-looking tags first), otherwise
the commit SHA. --no-tags always pins SHAs. Repositories that are not
cloned are listed as a warning and must be fixed before the file is usable.
of release plan <env> <db> <lockfile>¶
Dry run: which repositories would change (current HEAD → target ref) and
which installed modules are already out of sync (manifest version ≠
ir_module_module.latest_version). The final list of modules to update is
only known after the checkout, so apply recomputes it.
of release apply <env> <db> <lockfile>¶
- Validate — every repository of the environment must be pinned by the
lockfile (
--allow-partiallets unpinned repositories keep their branch); the lockfile'sprojectmust match. - Plan — repositories and modules, then ask for confirmation (
--yes). If no repository changes and no installed module changed version,applywrites anoopledger entry and stops: nothing is backed up or restarted. - Backup —
generate_backup_odoo()(DB dump + filestore, streamed) into<env>/backups/;--no-backupskips it (not for production). A failed backup aborts before any change. - Checkout —
clone_or_update_repos()withrevset from the lockfile, only for the repositories whose ref changes (unchanged ones are not fetched). If the lockfile carriescommit, the resolved HEAD must start with it (a tag that moved aborts). On failure every repository is put back at its previous commit..project.lockand the addons paths are refreshed. - Update —
-uof the installed modules whose manifest version differs from the database (series prefix is normalised:1.2.3==19.0.1.2.3), with the same Odoo stop/update/start isolation and state verification asof env update.--update-allruns-u all;--no-updateskips this step. No bump, no update. - Restart + smoke test — Odoo restarted;
/web/loginmust answer 200 within 2 minutes; no module may be left into upgrade/to install/to remove; active crons, active mail servers anddatabase.is_neutralizedare compared before/after and a difference is reported as a warning (a module update that re-activated a cron or a mail server is exactly what you want to know before opening the environment). - Offload — when
project.backup_remote(orOF_BACKUP_REMOTE) names an rclone remote, the pre-deploy backup is moved to<remote>/<project>/<env>/<file>after the checks passed (--keep-backup-localto skip). A failed release keeps the backup on disk: it is the rollback. - Ledger — one JSON line appended to
<env>/releases.log:
{"ts": "...", "action": "release.apply", "actor": "hector", "project": "nikro",
"environment": "main", "db": "nikro", "lockfile": "/srv/cliente_nikro/envs/main.lock.yml",
"lockfile_sha256": "...", "repos_changed": [{"repo": "payment-siro-roela-ce", "from": "0eb9c49…", "to": "v19.0.2.2.6"}],
"modules": ["payment_siro_roela"], "backup": "nikro_2026-10-03_18-30-01.zip",
"checks": {"http_login_200": true, "no_pending_modules": true, "crons_unchanged": true, ...},
"result": "success", "duration_s": 94.2}
The actor is RELEASE_ACTOR if set (CI runners should set it to the
person who triggered the workflow), else SUDO_USER / USER.
Exit code 1 and "result": "error" when the update failed, the login page
does not answer or modules are left pending. The command then prints the
rollback: of env restore <env> <env>/backups/<backup> --db <db> followed
by applying the previous lockfile. It is never run automatically — restoring
a production database is a decision, not a reflex.
--json prints the ledger record instead of the human output.
of release log <env> [-n N]¶
Show the last N ledger entries of an environment.
Pinned environments and of env update -r¶
After apply, every repository is checked out at the pinned ref (detached
HEAD when the ref is a commit or a tag). of env update -r still follows
project.yml: it puts each repository back on its branch and pulls the tip,
i.e. it takes the environment out of the release and back to "developer
mode". Use it on stg; on test/main promote with of release apply only.
Rollback¶
- Code only (nothing was updated, or you want the previous refs back):
of release apply <env> <db> <previous lockfile>. - A module update that went wrong: restore the backup printed by
apply, then apply the previous lockfile. A module-uis not reversible by checking out old code.