CLI-first Odoo-aware deployment platform for self-hosted Odoo projects
Project description
odooctl
odooctl is a CLI-first, Odoo-aware control plane for self-hosted Odoo on Docker Compose — think open-source Odoo.sh for your own server. 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. It runs against the compose stack you already have; you keep your server, your data, and your compose files.
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.
- 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 backups, safe restores | odooctl backup --verify, 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 drills | odooctl dr drill |
docs/disaster-recovery.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 | odooctl schedule |
docs/getting-started.md |
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 venv
uv pip install -e '.[dev]'
pytest -q # unit suite
pytest -m integration tests/integration # opt-in real-Odoo matrix (needs Docker)
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.
- 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file odooctl-0.2.0.tar.gz.
File metadata
- Download URL: odooctl-0.2.0.tar.gz
- Upload date:
- Size: 310.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54fb15417b20d1a3371ac5bfaaaa499a930cdffa425cfc629622deb49638a477
|
|
| MD5 |
0c98391264a3ac3391890a596024999c
|
|
| BLAKE2b-256 |
366bfa8fd329be04770767e09943cb21386136e7056c3e15004dd327c61cdc38
|
Provenance
The following attestation bundles were made for odooctl-0.2.0.tar.gz:
Publisher:
release.yml on odooctl/odooctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
odooctl-0.2.0.tar.gz -
Subject digest:
54fb15417b20d1a3371ac5bfaaaa499a930cdffa425cfc629622deb49638a477 - Sigstore transparency entry: 2205623218
- Sigstore integration time:
-
Permalink:
odooctl/odooctl@a1f6867c7debc10764f608b7cdf9aec38ecb4b33 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/odooctl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a1f6867c7debc10764f608b7cdf9aec38ecb4b33 -
Trigger Event:
push
-
Statement type:
File details
Details for the file odooctl-0.2.0-py3-none-any.whl.
File metadata
- Download URL: odooctl-0.2.0-py3-none-any.whl
- Upload date:
- Size: 184.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95da06676f5a590367b4fd5725b3034dc6792109729e129891554d61accb2e56
|
|
| MD5 |
025272347fca1e8548d3edd8e09af69b
|
|
| BLAKE2b-256 |
6f5e9a8ba80ecfc79756413e17e5d0b6e456c8c81e242b763698db87e593cdd5
|
Provenance
The following attestation bundles were made for odooctl-0.2.0-py3-none-any.whl:
Publisher:
release.yml on odooctl/odooctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
odooctl-0.2.0-py3-none-any.whl -
Subject digest:
95da06676f5a590367b4fd5725b3034dc6792109729e129891554d61accb2e56 - Sigstore transparency entry: 2205623256
- Sigstore integration time:
-
Permalink:
odooctl/odooctl@a1f6867c7debc10764f608b7cdf9aec38ecb4b33 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/odooctl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a1f6867c7debc10764f608b7cdf9aec38ecb4b33 -
Trigger Event:
push
-
Statement type: