This release is a pre-release and may not be stable for production use.
odooctl
odooctl is a CLI-first, Odoo-aware control plane for self-hosted Odoo on
Docker Compose or Kubernetes — think open-source Odoo.sh for your own
infrastructure. It handles the operational lifecycle generic deploy tools miss:
verified backups, sanitized staging clones, module updates, rollback,
environment promotion, upgrade rehearsal, and health checks, all Odoo- and
PostgreSQL-aware. Existing Compose projects remain the zero-migration default.
Why odooctl
- Safety by default. Deploys to protected environments take a database + filestore backup first; failed module updates and failed health checks stop the deployment with a non-zero exit.
- Verify before destroy. Restores land in a temporary database and are only swapped into place after the restore succeeds — a bad backup never destroys a working environment.
- Off-host backup policy is explicit. S3 copies can be
required,best_effort, ordisabled, with byte verification, project-scoped object namespaces, and retention that never treats an unknown old upload as safe to delete. - Filestore migrations verify every byte. Local paths and Docker volumes can migrate to a POSIX object mount or a project-scoped S3-compatible generation; cutover is explicit and source deletion is a separate, fully-confirmed operation.
- Staging clones you can trust.
odooctl clone production stagingsanitizes by default: mail servers, crons, payment providers, queue jobs, OAuth secrets, IAP tokens, and webhook URLs are neutralized — including wiping Odoo 19 WebAuthn passkeys — so staging cannot email customers or charge cards. - Adopt what you already run.
odooctl importdetects an existing compose deployment read-only, previews the generated config, and only writes on--yes— then registers the project, runs preflight checks, and takes a safety backup. - Protected environments. Production-tier environments require elevated confirmation for destructive operations, in the CLI, API, and web UI alike.
- Secrets stay secret. Config references secrets by env-var name only; logs, errors, and streamed operation events are redacted.
- A local API and web UI, RBAC'd.
odooctl serveruns a localhost-only REST API plus a no-build web UI with viewer/operator/admin/owner roles, HMAC bearer tokens, and a privileged runner kept in a separate process. - Upgrade rehearsal, not upgrade roulette.
odooctl migrate rehearseclones production into a throwaway DB, runs the (OpenUpgrade-backed) upgrade there, health-checks, and writes a report. Production is never touched.
Quickstart
A. Adopt an existing Docker Compose deployment
# Read-only detection and preview — no containers touched, no secrets stored
odooctl import /srv/odoo/acme
# Adopt: write odooctl.yml, register the project, run doctor, take a safety backup
odooctl import /srv/odoo/acme --name acme --yes
# First verified backup, then a sanitized staging environment in one command
odooctl -p acme backup production --verify
odooctl -p acme env create staging --clone-from production
B. Start a fresh project
mkdir acme && cd acme
odooctl setup --name acme --stack odoo-19-community # or run interactively: odooctl setup
setup scaffolds odooctl.yml from a pinned stack template (secrets referenced by env-var name, never inlined). Edit the domain, database names, and your docker-compose.yml, then:
export ODOO_DB_PASSWORD='a-strong-secret'
odooctl validate
odooctl doctor
odooctl deploy production --branch main
Either way, day-2 operations look like:
odooctl backup production --verify
odooctl clone production staging --sanitize
odooctl update-modules staging --modules sale,stock
odooctl restore staging --backup latest
odooctl rollback production --mode code
odooctl migrate rehearse --env production --to 19.0 --openupgrade
odooctl status
Features
| Feature | Command(s) | Docs |
|---|---|---|
| Import / takeover of existing stacks | odooctl import |
docs/getting-started.md |
| Deploy with pre-deploy backups | odooctl deploy |
docs/deployment.md |
| Verified local/remote backups, safe restores | odooctl backup --verify, odooctl backup-remote, odooctl restore |
docs/backup-restore.md |
| Sanitized staging clones | odooctl clone, odooctl env create |
docs/staging-clone.md, docs/environments.md |
| Rollback (code or full) | odooctl rollback |
docs/rollback.md |
| Environment promotion | odooctl promote |
docs/environments.md |
| Preflight checks | odooctl doctor |
docs/doctor.md |
| Upgrade rehearsal (17 → 18 → 19) | odooctl migrate matrix / scan / rehearse |
docs/migration.md |
| Disaster recovery and provider snapshots | odooctl dr drill, odooctl dr snapshot |
docs/disaster-recovery.md |
| PostgreSQL WAL archiving and PITR | odooctl pitr |
docs/pitr.md |
| Object-storage filestore migration | odooctl filestore |
docs/filestore-storage.md |
| Kubernetes runtime and production manifests | odooctl deploy, odooctl logs, odooctl status |
docs/kubernetes.md |
| GitOps and expiring PR environments | odooctl gitops |
docs/gitops.md |
| Progressive rollout and automated rollback | odooctl deploy, odooctl promote |
docs/progressive-deployment.md |
| k3d + Tilt production simulation | odooctl local |
docs/local-simulation.md |
| Domains / SSL via reverse proxy | odooctl domain |
docs/domains-ssl.md |
| Local REST API + operation queue | odooctl serve, odooctl runner, odooctl ops |
docs/api.md |
| RBAC, tokens, secret store | odooctl security |
docs/rbac.md |
| Stack / addon catalog | odooctl catalog, odooctl setup |
docs/catalog.md |
| Scheduled backups / checks / DR drills | odooctl schedule |
docs/backup-restore.md#scheduling-backups-verification-and-drills |
Provider snapshots are deliberately coarse DR artifacts: one provider source is bound to one environment, recovery is plan-only by default, and execution creates isolated resources only after exact snapshot/source confirmation. They supplement rather than replace portable database + filestore backups; see the lifecycle and cost notes.
Web UI
The optional web UI is a no-build vanilla-JS SPA served by the local API:
pip install 'odooctl[api]'
export ODOOCTL_API_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"
odooctl serve # binds 127.0.0.1:8787 by default
Open http://localhost:8787/ and paste a token minted with:
odooctl security token mint --action api --env "*" --project "*" --role operator
The server is localhost-only by design and speaks plain HTTP — do not bind it to a public address; use an SSH tunnel for remote access. The API process never touches Docker or PostgreSQL directly: privileged work is delegated to odooctl runner through a durable queue with short-lived, single-use capability tokens. See docs/web-ui.md and docs/api.md.
How it compares
Compared to Odoo.sh, odooctl is self-hosted and free: your server, your data, no per-worker pricing — with the same core workflow of environments, backups, staging builds, and upgrade testing. Compared to doodba, odooctl is not a project scaffolding framework; it manages the operational lifecycle (backup, clone, restore, promote, rehearse) of whatever compose project you already have — including one built with doodba. Compared to hand-rolled compose scripts, every destructive path here ships with tested safety rails: pre-deploy backups, restore-into-temp-then-swap, sanitization that is on by default, protected-environment confirmation, and secret redaction, backed by 700+ unit tests plus a real-Odoo integration suite.
Supported Odoo versions
Odoo 17, 18, and 19 (Community) are integration-tested: a disposable-stack harness in tests/integration/ runs the full operator lifecycle against real odoo:17/odoo:18/odoo:19 images. See docs/odoo-versions.md for per-version notes.
Install
PyPI publication is coming soon — pipx install odooctl / uv tool install odooctl will work once released. Today, install from source:
git clone https://github.com/odooctl/odooctl && cd odooctl
pipx install . # or: uv tool install .
odooctl --help
The host also needs Docker Engine with the Compose plugin and tar. With the recommended execution_mode: docker, PostgreSQL client tools run inside your DB container, so the host needs none. See docs/installation.md.
Development
uv sync --frozen --extra dev --extra api # exactly what CI installs
uv run --frozen pytest -q # unit suite
uv run --frozen ruff check odooctl tests # lint
uv run --frozen pytest -m integration tests/integration # opt-in real-Odoo matrix (needs Docker)
uv.lock is authoritative — --frozen keeps your environment identical to
CI's. See CONTRIBUTING.md for changing dependencies.
Safety defaults
- Deploys to protected environments create database and filestore backups first.
- Module-update and health-check failures fail the deployment with a non-zero exit.
- Restores go into a temporary database and are swapped in only after success.
- DR drills restore into disposable PostgreSQL and Odoo containers on an internal network with dedicated volumes; live services and data volumes are never restore targets.
- PITR restores into an isolated PostgreSQL runtime and a new verified database before an explicit, OID-fenced cutover. WAL recovery is database-only and never claims to rewind the Odoo filestore.
- Filestore migration cutover re-verifies both the target and unchanged source; source deletion is a separate operation requiring exact environment and migration confirmations.
- Clone sanitization is on by default and disables mail, fetchmail, crons, payment providers, queue jobs, and automation rules; scrubs OAuth/IAP/webhook credentials; deletes Odoo 19 passkeys; and rewrites and freezes
web.base.url. - Secrets are referenced via environment variables (
password_env) and redacted from logs, errors, and operation events; never commit secret values. - The API/runner split is structurally enforced:
odooctl security runner-checkverifies the API layer imports no privileged adapters.
See docs/security.md for the trust model.
Contributing, security, license
Contributions are welcome — see CONTRIBUTING.md and the Code of Conduct. Good entry points are issues labeled good first issue and help wanted. Report vulnerabilities privately per SECURITY.md.
odooctl is free software, dual-licensed: AGPL-3.0-or-later for open use — including running it commercially for yourself or your clients — with a commercial license available for embedding or reselling it in proprietary products. See LICENSING.md.
Metadata
Release files for odooctl 0.3.0b1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| odooctl-0.3.0b1.tar.gz | 518.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| odooctl-0.3.0b1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 858.1 kB
Release files / odooctl-0.3.0b1.tar.gz
| Download URL | odooctl-0.3.0b1.tar.gz |
|---|---|
| Size | 518.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e0dbe5d62f8f258a8c6e175e41194516464b6779fdf6e80e3f22110ff5025836
|
|
BLAKE2b-256 checksum How to use checksums |
8eba2fd73e6adc53526cb669efb2c6a48ae27d9e0c49beffb784c4704c8be1bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.
Transparency logRelease files / odooctl-0.3.0b1-py3-none-any.whl
| Download URL | odooctl-0.3.0b1-py3-none-any.whl |
|---|---|
| Size | 339.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
65f5f13b08a7a2295f1d5b064b36d7c43307fb22a23b04e93684a458298b8c31
|
|
BLAKE2b-256 checksum How to use checksums |
060b7d88ddec6c0d0024fc735524d109b7262a5c7f4e7c2d901334c3b43ee20c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.
Transparency log