Skip to content

IMIO/helm-odoo

Repository files navigation

Helm Chart for Odoo

License version test release

Introduction

This Helm chart installs Odoo in a Kubernetes cluster.

Important

This helm chart is designed for @IMIO specific needs and is not intended to resolve all use cases. But we are open to contributions and suggestions to improve this helm chart. This Helm chart targets Odoo 16.0+ using the official Odoo Docker image. The default image tag is 18.0; override with image.tag as needed.

Prerequisites

Note

For production environments, it is recommended to use CloudNativePG for PostgreSQL. The bundled chart is primarily intended for testing and development purposes, do not use it in production.

  • Kubernetes cluster 1.25+
  • Helm 3.8.0+
  • PV provisioner support in the underlying infrastructure.
  • Postgres DB (This chart can install a PostgreSQL database based on the CloudPirates postgres chart, pinned to PostgreSQL 17). We use it for testing purposes.

Why do we not use the bitnami/odoo chart?

  • we want to use the official Odoo Docker Image or our custom Odoo Docker Image.
  • we need some specific configuration for our Odoo instance.

Installation

Pull Helm release

helm repo add imio https://imio.github.io/helm-charts
helm repo update

Verify the chart signature

Since release 3.0.1, packaged charts are signed. See IMIO/helm-charts — Verify chart signatures for how to verify.

Configure the chart

The following items can be set via --set flag during installation or configured by editing the values.yaml directly (need to download the chart first).

See the values.yaml file for more information.

Install the chart

helm install [RELEASE_NAME] imio/odoo

or by cloning this repository:

git clone https://github.com/imio/helm-odoo.git
cd helm-odoo
helm dep up
helm upgrade odoo . -f values.yaml --namespace odoo --create-namespace --install

Configuration

The following table lists the configurable parameters of the helm-odoo chart and the default values.

See the values.yaml file for more information.

Database configuration

Database configuration is split into two clearly-scoped sections:

  • postgresql.* — configures the bundled CloudPirates postgres subchart (official docker.io/postgres image, pinned to PostgreSQL 17.x via postgresql.image.tag), used only when postgresql.enabled: true (intended for dev/test). The subchart is aliased to postgresql, so the values key and the <release-name>-postgresql resource names are preserved. Odoo's connection is taken from postgresql.auth.*, the host is auto-derived as <release-name>-postgresql and the port is 5432. postgresql.auth.username / .password / .database configure the superuser (this chart has no separate postgresPassword; the named user is the superuser and gets a same-named database), or supply the password through postgresql.auth.existingSecret (default key postgres-password).
  • externalDatabase.* — connection settings for an external PostgreSQL (e.g. CloudNativePG), used only when postgresql.enabled: false. externalDatabase.host is required in this mode (templating fails fast if it is empty).

Only one section is ever read at a time, depending on postgresql.enabled — no key is shared between the bundled and external scenarios.

Use an existing Secret for Odoo configuration

You can use an existing secret for the Odoo configuration.

In the values.yaml file, set the existingSecret.enabled parameter to true. Then, create a Secret in your namespace named <release-name>-odoo-conf (or <fullnameOverride>-odoo-conf if fullnameOverride is set), containing the odoo.conf key.

Use external-secrets.io for Odoo configuration

In the values.yaml file, set the externalsecrets.enabled parameter to true.

You need to have the external-secrets.io operator installed in your cluster. See the external-secrets.io documentation for more information.

Two Vault keys are used: postgresqlKey (the PostgreSQL credentials — DB user password + superuser) and odooKey (Odoo's master admin_passwd). Each credential written into odoo.conf chooses its own key + property under externalsecrets.properties.odoo, so the DB password is stored in Vault once and shared with the PostgreSQL secret instead of being duplicated:

externalsecrets:
  enabled: true
  secretStoreRef:
    name: vault-backend
  postgresqlKey: secret/data/odoo/db      # DB user password + superuser
  odooKey: secret/data/odoo/app           # Odoo master password
  properties:
    postgresql:
      password: postgresql-password
      adminPassword: postgresql-admin-password
    odoo:
      postgresqlPassword:                 # odoo.conf db_password
        key: ""                           # "" -> postgresqlKey (falls back to odooKey)
        property: postgresql-password
      adminPasswd:                        # odoo.conf admin_passwd
        key: ""                           # "" -> odooKey
        property: odoo-admin-passwd
  • Share the DB password (default): leave postgresqlPassword.key empty. Both the PostgreSQL secret and odoo.conf's db_password read postgresqlKey — one value in Vault. To make all Odoo/DB secrets live in a single key, set postgresqlKey == odooKey.
  • Split keys: set postgresqlPassword.key (or adminPasswd.key) to any other Vault key to read that credential from a different location.
  • External DB (postgresql.enabled: false): the <release>-postgresql-secret is not rendered; leave postgresqlKey unset and set only odooKeydb_password falls back to odooKey (the pre-2.0.0 behavior).

Warning

existingSecret.enabled and externalsecrets.enabled are mutually exclusive. Enabling both will cause helm install/helm upgrade to fail with an explicit error. Choose exactly one secret backend, or leave both disabled to have the chart generate secrets from values.yaml.

Database initialization and updates

Database lifecycle is handled by Helm hook Jobs that run at pre-install and pre-upgrade — before the Odoo/cron deployments are created or re-applied. Each Job scales any running Odoo/cron to 0, waits for the database to be reachable, then runs the migration; Helm brings the deployments back up afterwards.

Initialization (odoo.init)

odoo:
  init:
    enabled: true
    modules: base,web

Runs odoo -i <modules> -d <db> --stop-after-init. Treat enabled as a one-shot intent: set it true to initialise on install or to re-initialise after wiping the DB, then set it back to false — left on, every upgrade re-runs odoo -i (which scales Odoo to 0 for the duration, even though the command itself is a no-op on an already-installed database).

Updates (odoo.update)

odoo:
  update:
    enabled: true       # set true only for a version bump (causes downtime + migration)
    modules: all
    maintenancePage: true

Runs odoo -u <modules> -d <db> --stop-after-init (requires an already-initialised DB). With maintenancePage: true (requires ingress.enabled), a temporary maintenance pod is created that the <release>-nginx Service routes to while Odoo is scaled to 0, without touching the ingress.

Important

Set odoo.update.enabled back to false after the upgrade — left on, a scale-to-0 migration runs on every helm upgrade.

Note

Maintenance mode does not disable these hooks. maintenance.enabled: true only scales Odoo/cron to 0 and serves the maintenance page — the init/update Jobs still run on the next helm install/helm upgrade. This is intentional: you can migrate behind the maintenance page (set maintenance.enabled: true, then upgrade with odoo.update.enabled: true, then reopen). If you enter maintenance for something that must not touch the DB (e.g. a restore), make sure odoo.init.enabled and odoo.update.enabled are false. When both maintenance and a hook are enabled, helm install/helm upgrade prints a warning in its notes.

Note

Bundled PostgreSQL + hooks: Helm cannot deploy a subchart before the parent's pre-install hooks, so postgresql.enabled: true is not up when the Jobs run. For dev/test, run PostgreSQL as a pre-install hook via postgresql.commonAnnotations (helm.sh/hook: pre-install, weight below 0) — see test/local.yaml. Production should use an external database (postgresql.enabled: false + externalDatabase.*).

Scheduled rollout restart (rolloutRestart)

Optionally schedule a periodic kubectl rollout restart to recycle the Odoo pods (e.g. to clear leaked memory or pick up a freshly pulled image tag). When enabled, the chart renders a CronJob plus a dedicated ServiceAccount/Role/RoleBinding (<release>-rollout-restart) scoped to get/list/watch/patch on deployments in the release namespace.

rolloutRestart:
  enabled: true
  schedule: "0 3 * * 0"   # weekly, Sunday 03:00 (cluster timezone / UTC)
  # targets: []           # default: <release> + <release>-cron (when cron.enabled)

On each run it executes kubectl rollout restart followed by kubectl rollout status for every target deployment. When targets is empty (the default) it restarts the main Odoo deployment and, if cron.enabled, the cron deployment too; set targets to an explicit list of deployment names to override. concurrencyPolicy: Forbid prevents overlapping runs. The kubectl image defaults to odoo.hooks.kubectlImage and can be overridden via rolloutRestart.image.

Prometheus ServiceMonitor (serviceMonitor)

Optionally expose Odoo metrics to a Prometheus Operator install (e.g. kube-prometheus-stack). When enabled, the chart renders a ServiceMonitor (monitoring.coreos.com/v1) that selects the main Odoo Service (<release>-odoo, app.kubernetes.io/component: server) and scrapes the named odoo-http port — the cron Service is deliberately excluded.

serviceMonitor:
  enabled: true
  labels:
    release: kube-prometheus-stack   # match your Prometheus serviceMonitorSelector
  path: /metrics
  interval: 30s

Requires the Prometheus Operator CRDs to be installed in the cluster, and an Odoo endpoint that actually serves metrics at path on the scraped port (e.g. a metrics-exporting Odoo module) — the chart only wires the scrape, it does not add a metrics endpoint. Most operators discover ServiceMonitors by a label (commonly release: <prometheus-release>); set it via serviceMonitor.labels. scrapeTimeout, scheme, relabelings and metricRelabelings are also configurable — see values.yaml.

Production readiness checklist

The chart ships safe-by-default security settings (non-root containers, dropped capabilities, a dedicated ServiceAccount with the API token disabled), but a few things still need explicit configuration for production:

  • Persistence — set persistence.enabled: true with an appropriate storageClass/size. Without it the filestore lives on an emptyDir and Odoo attachments/sessions are lost on pod restart. For more than one replica the filestore must be ReadWriteMany.
  • External database — set postgresql.enabled: false and configure externalDatabase.* (e.g. CloudNativePG). The bundled CloudPirates PostgreSQL uses ephemeral storage and is dev/test only.
  • Secrets — use existingSecret.enabled: true or externalsecrets.enabled: true instead of inline odoo.admin_passwd / externalDatabase.password so credentials never live in plaintext values.
  • Resources — set resources (Odoo), nginx.resources and cron.resources.
  • securityContext — defaults assume the official Odoo image (odoo user, uid 100 / gid 101). If you use a custom image with a different user, override securityContext / containerSecurityContext / nginx.containerSecurityContext accordingly.

Local Setup for development

Create a kind cluster:

cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
EOF

Install the Nginx Ingress Controller:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

Create the odoo namespace:

kubectl create namespace odoo

Get the IP address of the kind-control-plane:

 docker container inspect kind-control-plane --format '{{ .NetworkSettings.Networks.kind.IPAddress }}'

Modify the /etc/hosts file with the IP address of the kind-control-plane and add the hostnames:

nano /etc/hosts
172.18.0.2 odoo.local

Modify the test/local.yaml file and run the helm chart:

helm dep up
helm upgrade odoo . -f test/local.yaml --namespace odoo --create-namespace --install

Cleanup:

helm uninstall odoo -n odoo

# Delete resources not managed by Helm (hook Jobs/RBAC left behind by uninstall)
kubectl delete all,serviceaccount,networkpolicies \
  -l app.kubernetes.io/instance=odoo \
  -n odoo --ignore-not-found

kind delete cluster

Contributing

Feel free to contribute by making a pull request.

Please read the official Helm Contribution Guide from Helm for more information on how you can contribute to this Chart.

Upgrading

To 3.0.0

The bundled PostgreSQL subchart was swapped from bitnami/postgresql (which moved to the unmaintained bitnamilegacy Docker Hub namespace) to the maintained CloudPirates postgres chart on the official docker.io/postgres image, pinned to PostgreSQL 17. This only affects postgresql.enabled: true (bundled DB, dev/test) — external-database users (postgresql.enabled: false + externalDatabase.*) are unaffected.

The new chart is aliased to postgresql, so the values key stays postgresql.* and the generated resource names are unchanged (<release>-postgresql Service/Secret, port 5432) — no connection changes are needed. The postgresql.* schema changed, though:

  • Removed postgresql.auth.postgresPassword — this chart has a single superuser. postgresql.auth.username / .password / .database now configure that superuser (Odoo connects as it and gets a same-named database).
  • Removed postgresql.global.security.allowInsecureImages and postgresql.image.repository (bitnami-legacy specifics) — no longer needed.
  • Added postgresql.image.tag, pinned to a PostgreSQL 17.x digest.
  • Renamed postgresql.primary.persistencepostgresql.persistence (still enabled: false, ephemeral, for dev/test).

The bundled DB is ephemeral and dev/test only, so there is no data migration — just re-render with the new values. helm dep up will pull the new subchart (removing the old bitnami tarball).

To 2.0.0

externalsecrets.properties.odoo.postgresqlPassword and .adminPasswd changed from a bare string (the remoteRef property name) to a map {key, property}, so each credential can now pick its own Vault key — this is what lets the DB password be shared with the PostgreSQL secret instead of duplicated. If you overrode either property name, convert it:

# before (<= 1.3.0)
externalsecrets:
  properties:
    odoo:
      postgresqlPassword: postgresql-password
      adminPasswd: odoo-admin-passwd

# after (2.0.0)
externalsecrets:
  properties:
    odoo:
      postgresqlPassword:
        property: postgresql-password
        # key: ""   # optional; "" -> postgresqlKey -> odooKey
      adminPasswd:
        property: odoo-admin-passwd
        # key: ""   # optional; "" -> odooKey

If your override only repeated the defaults (postgresql-password / odoo-admin-passwd), just remove it and let the chart defaults apply; leaving these unset is also valid. Only externalsecrets.enabled: true users who set these are affected. Skipping the conversion makes helm upgrade fail at render time (cannot overwrite table with non table / interface conversion: interface {} is string, not map) — a blocking error, never a silent wrong value.

To 1.0.0

Important

TL;DR

  • Before upgrading, delete the old Deployment(s) — the selector is now immutable: kubectl delete deployment <release>-odoo <release>-odoo-cron -n <ns> --ignore-not-found
  • DB connection values moved: postgresql.host/port/authexternalDatabase.* (external DB), while postgresql.* now configures only the bundled PostgreSQL subchart.
  • odoo.update.enabled now defaults to false — init/update run as Helm hook Jobs, not in the main pod.
  • Containers are non-root by default (official Odoo image: uid 100 / gid 101) and get a dedicated ServiceAccount with its token disabled. Custom images with a different user must override securityContext / containerSecurityContext.

This is a breaking release. There are three independent migrations to apply to your values.

1. PostgreSQL configuration split

The single postgresql: section that previously held both the bundled-chart config and the database connection settings has been split into two clearly-scoped sections (postgresql.* for the bundled PostgreSQL subchart, externalDatabase.* for an external database). Update your values as follows:

  • postgresql.host / postgresql.portexternalDatabase.host / externalDatabase.port (the host is now auto-derived as <release-name>-postgresql for the bundled chart).
  • postgresql.auth.admin_passwordpostgresql.auth.postgresPassword (bitnami-native key).
  • External-database connection now lives under externalDatabase.* (read only when postgresql.enabled: false).

2. Init and update moved to Helm hook Jobs

Database initialization and updates are no longer run inside the main deployment — the old init-db-odoo init container and the odoo --update … command override have been removed in favor of Helm hook Jobs. Notable changes:

  • odoo.update.enabled now defaults to false (was true). Previously the running Odoo container ran odoo --update on every start; now an enabled update is a pre-install,pre-upgrade hook Job that scales Odoo to 0 and migrates. Enable it only to upgrade Odoo modules, then set it back to false.
  • odoo.init.enabled now drives a pre-install,pre-upgrade hook Job (a single Job, not per-replica / per-restart) that runs before Odoo starts; it can be re-run to re-initialise a wiped database via helm upgrade.
  • New value groups: odoo.update.maintenancePage and odoo.hooks.* (backoffLimit, ttlSecondsAfterFinished, waitForDb, kubectlImage).

Note

The update hook needs permission to scale the deployments to 0. The chart creates a namespaced ServiceAccount/Role/RoleBinding for this automatically, rendered only while odoo.update.enabled: true.

3. Service/Deployment selector scoping + security hardening

The main Odoo Deployment and the <release>-odoo / <release>-odoo-longpolling Services are now scoped with app.kubernetes.io/component: server so they do not select the cron pods (which share the base selector labels and also expose port 8069). This release also adds non-root securityContext defaults and a dedicated ServiceAccount with the API token disabled.

Warning

A Deployment's spec.selector is immutable, so helm upgrade from an earlier release fails with field is immutable. Delete the old Deployment(s) first:

kubectl delete deployment <release>-odoo <release>-odoo-cron \
  --namespace <namespace> --ignore-not-found
helm upgrade <release> . -f <values> --namespace <namespace>

If you run a custom Odoo image whose user is not uid 100 / gid 101, override securityContext / containerSecurityContext to match.

License

Apache License 2.0

About

Helm Chart for Odoo

Topics

Resources

License

Stars

13 stars

Watchers

2 watching

Forks

Packages

 
 
 

Contributors