Multi-version Odoo
Audience
PLANA staff and customers planning a version upgrade. Covers how we keep v17, v18 and v19 working in production side-by-side.
The upstream Odoo community ships a new major release every October. We support that cadence by running the current major and the previous one in full production, with the next major in limited release while we wait for the OCA community to finish porting the dependencies we use.
| Year | Default | Also supported | Limited |
|---|---|---|---|
| 2026 (now) | v18 | v17 (legacy, EOL 2027) | v19 (some OCA modules pending) |
| 2027 | v19 | v18 | v20 (limited) |
| 2028 | v20 | v19 | v21 (limited) |
A tenant stays on its current major until PLANA verifies that every module it uses is available on the target version. Migrations are operator-driven, not customer-driven.
How the cluster is laid out
Three sibling namespaces, one per major:
| Namespace | Major | Status (2026-05) |
|---|---|---|
plana-odoo | 17 | Empty — last v17 tenant migrated 2026-05-22 |
plana-odoo-18 | 18 | Production — default for new tenants |
plana-odoo-19 | 19 | Limited release |
Each namespace has:
- Its own
worker-odooDeployment with HPA - Its own
worker-odoo-testDeployment for staging clones - Its own filestore PV (RWX, NFS-backed)
- Its own ResourceQuota
- Its own redis-allow-consumers NetworkPolicy entry
- Its own
provider-sql-postgres-credssecret (copy of the one incrossplane-system) - Its own
forgejo-registryimage-pull secret
The shared eg-gateway Gateway resource lives in plana-odoo and stays there permanently — even when plana-odoo has no tenants. HTTPRoutes in other namespaces reference it across namespaces via ReferenceGrant.
Where the source lives
The Odoo monorepo is branch-per-version at git.planapulse.com/plana-pulse/odoo-modules:
| Branch | Tracks | Tag |
|---|---|---|
17.0 | Odoo 17 | base-17:<sha> |
18.0 | Odoo 18 | base-18:<sha> |
19.0 | Odoo 19 | base-19:<sha> |
Plus -upgrade variants used by the cross-major migration path (base-{N}-upgrade), which include the OpenUpgrade framework.
A single Dockerfile parametrized by ARG ODOO_VERSION builds all three. Cherry-picks flow 17.0 → 18.0 → 19.0 chronologically. A platform change is committed once and ported forward; a port is committed once per version branch.
Per-major module set
What ships on each major depends on the OCA community's porting cadence. Today (2026-05):
| Module group | v17 | v18 | v19 |
|---|---|---|---|
plana_* modules (PLANA-built) | ✓ | ✓ | ✓ |
OCA web, server-auth, server-backend, server-ux | ✓ | ✓ | ✓ |
OCA account-financial-tools, account-financial-reporting | ✓ | ✓ | ✓ |
OCA l10n-bulgaria (BG fiscal pack) | ✓ | ✓ | ✓ |
OCA account-analytic, account_analytic_sequence | ✓ | ✓ | — pending |
OCA agreement, agreement_legal | ✓ | ✓ | — pending |
OCA helpdesk (7 modules) | ✓ | ✓ | — pending |
OCA dms | ✓ | ✓ | — pending |
PLANA does not back-port OCA modules. We follow the upstream community branches and pick them up as they land. A weekly cron (oca-sync.yml workflow) refreshes the OCA submodules on all three version branches every Saturday.
Provisioning a tenant on a specific version
A PLANAClient CR with spec.odooVersion selects the major. The Composition's transforms: map: translates the version into the namespace where the worker lives:
apiVersion: planapulse.com/v1alpha1
kind: PLANAClient
metadata: { name: acme }
spec:
subdomain: acme
projectId: 23
tier: pro
odooVersion: "18"→ HTTPRoute backendRefs.namespace=plana-odoo-18. Database created on pg01. Filestore PVC subdir created under the v18 NFS export. Backup CronJob scoped to the right namespace. See Crossplane for the full fan-out.
XRD defaults were bumped to "18" on 2026-05-22 — any new CR without an explicit version targets v18.
Same-major vs cross-major upgrades
| Path | Pattern | Vehicle |
|---|---|---|
Same major (v17 → v17) | Module set changed, code update | odoo --update=all in a Job |
Cross major (v17 → v18, v18 → v19) | Framework + schema migration | OpenUpgrade two-pass |
The cross-major two-pass:
- Pass 1 under the source-version binary (
base-17-upgrade):odoo -i openupgrade_framework --stop-after-init— installs the OpenUpgrade framework module on the DB so its module-loader patches activate. - Pass 2 under the target-version binary (
base-18-upgrade):agreement_legalSQL pre-fix (DELETE the two staleir_model_datarows that cause a unique-constraint violation), thenodoo --update=all --load=base,web,openupgrade_framework.
Both passes run via the TenantUpgrade Composition. The strategy is selected by spec.strategy: same-major | snapshot-then-upgrade.
The filestore is not migrated by the upgrade — that is a separate tar-pipe between holder Pods in the source and target namespaces. The runbook is at Operations → Upgrading a tenant.
Annual port cycle
When upstream Odoo ships a new major (October):
| Week | Action |
|---|---|
| W0 | Bootstrap a new branch from N: git checkout -b N+1. Bump ARG ODOO_VERSION. |
| W1–W3 | Port PLANA modules in dependency order |
| W4–W5 | Port OCA fork patches; rebase -planapulse branches; file upstream PRs |
| W6 | Stand up plana-odoo-N+1-staging namespace; build TemplateSnapshot{odooVersion=N+1} |
| W7–W8 | Pilot 2–3 friendly tenants via TenantUpgrade |
| W9–W10 | GA to new tenants; tier supported_odoo_versions += N+1 |
| W11–W12 | Bulk migrate willing tenants; mark N-1 branch protected/read-only |
Recurring effort: ~25–30 engineer-days/year. The framework was built once in 2026; subsequent cycles re-run the same checklist.
Known API breaks port to port
Captured during the 2026-05-21 v18 + v19 rollout — the patterns repeat between any two majors:
- Field renames — e.g.
project.project.analytic_account_id→account_idin v18. Search the codebase for the old name and update. - Import path moves —
from odoo import registrywas removed in v19; useodoo.modules.registry.Registryinstead. We work around this with try/except aliases so the same module file compiles on multiple majors. - Bus renames —
ImBus→BusBusin v19. Same try/except aliasing. - Enterprise dependencies — modules marked as Enterprise on a newer major must be dropped from the tier seed data. We are Community-only.
- View tag renames —
<tree>→<list>in v18. Bulk rename across all view XML files. - Base image UID change — v17 ran as UID 101; v18 and v19 run as UID 100. Per-namespace
runAsUserand filestore ownership must match. - Debian PEP 668 — Odoo 18 base is Debian 12;
pip installrequires--break-system-packages.
These are captured in infra/docs/runbooks/annual-odoo-port.md for next year's cycle. We treat the port runbook as a living checklist, updated during each port.
Where to read more
- Crossplane — how the version selector flows from the XR to the right namespace
- Operations → Upgrading a tenant
- Operations → Annual Odoo port
- PLANA Business Cloud → Version differences — the customer-facing view of the same content