# OdooSky v3 instance-template-v3 — default values. # # Per-tenant overlay repos override only the keys that differ from these # defaults. Keep this file as the single source of truth for what an # Odoo instance looks like by default; do not duplicate defaults in the # overlay schema or in Tower-Go. instance: # Short slug used in K8s object names + as the Helm release name. # Must be DNS-safe (lowercase, no underscores, <= 40 chars). code: demo # The full HTTPS hostname this instance answers on. # Tenants live under *.tenants.odoosky.org (covered by wildcard DNS A). domain: demo.tenants.odoosky.org # Named size — looked up against the `sizes` table below to derive # CPU / memory limits + Odoo workers. Per-tenant overlays only need # `instance.size: medium` (etc); they don't have to know the numbers. size: small # tenant — owning tenant identity. Currently only required when # postgres.passwordVaultPath is set (the ESO path needs to know which # tenant subtree to read from OpenBao). Tower writes `tenant.id` into # every new overlay; legacy overlays without ESO leave this empty. tenant: id: "" # The named-size table. Single source of truth for what each instance # tier actually gets. Adjust here, ALL future instances pick up the # new defaults on next reconcile. Existing instances keep their # previously-rendered manifests until ArgoCD re-syncs. # # Numbers are TOTAL across Odoo + Postgres containers. Odoo/PG split # is roughly 70/30 (matches v2 production tuning). Workers follow # the standard `(cpus*2 - 1)` heuristic capped by RAM headroom. # # tier total RAM total CPU PVC (fs/db) workers use case # tiny 1.5 GB 1.0 5/5 GB 0 sandbox/dev only — NOT recommended for production # small 3 GB 2.0 10/10 GB 1 5–15 users — RECOMMENDED MINIMUM # medium 6 GB 4.0 20/20 GB 2 15–50 users # large 12 GB 8.0 50/50 GB 4 50–150 users # custom operator operator operator operator HA / enterprise — wizard collects all fields sizes: tiny: odoo: requests: { memory: 384Mi, cpu: 200m } limits: { memory: 1Gi, cpu: 700m } postgres: requests: { memory: 128Mi, cpu: 100m } limits: { memory: 512Mi, cpu: 300m } storage: filestore: 5Gi database: 5Gi small: odoo: requests: { memory: 1Gi, cpu: 500m } limits: { memory: 2Gi, cpu: "1" } postgres: requests: { memory: 512Mi, cpu: 250m } limits: { memory: 1Gi, cpu: "1" } storage: filestore: 10Gi database: 10Gi medium: odoo: requests: { memory: 2Gi, cpu: "1" } limits: { memory: 4Gi, cpu: "2" } postgres: requests: { memory: 1Gi, cpu: 500m } limits: { memory: 2Gi, cpu: "2" } storage: filestore: 25Gi database: 25Gi large: odoo: requests: { memory: 4Gi, cpu: "2" } limits: { memory: 8Gi, cpu: "5" } postgres: requests: { memory: 2Gi, cpu: "1" } limits: { memory: 4Gi, cpu: "3" } storage: filestore: 50Gi database: 100Gi # imageMirror — REQUIRED for production. Customer instances must pull # their Odoo + Postgres images from the OdooSky-controlled registry, # never from Docker Hub directly. Three reasons: # # 1. Determinism. Docker Hub's `odoo:18.0` is a rolling tag — every # pod restart picks up whatever the latest nightly is. The 2026-05-04 # build shipped a SQL regression (now() - INTERVAL '15 minutes' # string-quoted) that broke every login on every new pod. We pin # to a specific date-stamped tag we tested. # 2. Air-gap. Customers running disconnected clusters can't reach # Docker Hub; they can reach our registry. # 3. Rate-limit immunity. Docker Hub anonymous pulls cap at ~100/6h # per IP. A cluster with 50 instances bouncing pods can hit that. # # Pinned tags are tracked in the `odoo-tower/odoosky-odoo` Gitea repo # (versions.yaml). Bumping that repo + this file is the GitOps path # for Odoo image updates. See the bump policy in odoosky-odoo/README.md. imageMirror: registry: "registry.odoosky.cloud/odoosky/docker-mirror" # pullSecret — name of a Secret in the instance namespace carrying # registry credentials. Provisioned per-cluster by cluster-platform-v3. pullSecret: "docker-mirror-pull" odoo: image: odoo # `tag` may be either a MAJOR reference ("18.0") or a literal pinned # tag ("18.0-20260421"). When it's a major, the chart resolves it via # `pinnedTags` below — that's the GitOps-clean path. Per-instance # overlays should carry the major; the chart owns the exact nightly. # Literal tags here are an escape hatch for staging tests. tag: "18.0" # pinnedTags — major → exact upstream nightly we have tested. # MUST stay in sync with odoo-tower/odoosky-odoo/versions.yaml; that # repo is the source of truth + bump policy. To bump: # 1. Test the candidate nightly (see odoosky-odoo README). # 2. Mirror it: nerdctl pull → tag → push to docker-mirror. # 3. Update BOTH versions.yaml AND this map in the same PR. # 4. ArgoCD reconciles; every instance of that major picks up the # new image on next pod restart, no overlay edits. # # Adding a new major: add an entry here. Tower's renderer writes the # major as `odoo.tag` — adding "17.0" / "19.0" here lights up that # major across every instance that asks for it. pinnedTags: "19.0": "19.0-20260421" "18.0": "18.0-20260421" "17.0": "17.0-20260421" "16.0": "16.0-20250909" # 16.0 reached EOL upstream Sep 2025; this is the final nightly # Filestore PVC size (Odoo's /var/lib/odoo). filestoreSize: 10Gi # Addons selected for this instance. Each entry is a tagged image in # the cluster-local registry (deployed by cluster-platform-v3 chart). # The chart renders one initContainer per entry that copies the # addon's content into a shared volume; the Odoo container reads from # /mnt/extra-addons/. # # Tower owns this list — it commits new entries to the tenant overlay # AFTER ensuring the corresponding image exists in the destination # cluster's registry (spawning a build Job from Gitea source if not). # # Schema: # addons: # - code: odoosky_demo # version: "18.0.1.0.0" # source: platform # platform | tenant # image: registry.odoosky-system.svc.cluster.local:5000/addons/odoosky_demo # # Empty list = no extra addons; only Odoo's built-in modules. addons: [] # Path inside the Odoo container where addons are materialized. # Odoo's addons_path includes this dir; one folder per addon code. # Override only if you need a non-standard layout. addonsMountPath: /mnt/extra-addons postgres: image: postgres tag: "16-alpine" user: odoo database: postgres # If empty, the chart auto-generates on first install and re-reads # the existing Secret on subsequent upgrades (lookup pattern). # Ignored when `passwordVaultPath` is set — ESO sources the password # from OpenBao instead. password: "" # passwordVaultPath — when set, the chart renders an ExternalSecret # pulling the password from OpenBao at # `tenants//instances//pg` (field # `password`). The legacy postgres-secret.yaml template is gated # off; the ExternalSecret produces the same `-pg` Secret # shape so postgres-statefulset.yaml is unchanged. # # When empty (default), the chart falls back to the legacy # postgres-secret.yaml path using `.password` above. Tower sets # this field on every new instance starting v0.77; pre-v0.77 # instances stay on the legacy path until migrated by the one-shot # tool (Chunk 3 of A-OpenBao). # # The actual path resolution is hardcoded in the # postgres-password-externalsecret.yaml template; this field is # the on/off toggle. Setting it to any non-empty value is # equivalent ("use ESO for this instance"); the path string itself # is currently advisory (it's the OpenBao subtree the operator can # `bao kv list` to find the password). passwordVaultPath: "" # externalSecretsStoreRef — ClusterSecretStore name the # ExternalSecret references. Provisioned by cluster-platform-v3 # under `openbao-platform` by default. Override only on clusters # that name the store differently. externalSecretsStoreRef: openbao-platform storage: 10Gi backups: enabled: true # Cron schedule for the automatic backup job. Default 03:00 UTC # daily — quiet hour for most timezones, non-business in EU/US/AS. schedule: "0 3 * * *" # How many dumps to retain in S3. The backup job prunes older # objects matching the instance's prefix on every successful run. retain: 7 # S3-compatible destination. The endpoint + region + bucket are # NON-secret and live in this committed values.yaml; the AWS # credentials live in a K8s Secret named by `credentialsSecret`, # provisioned out-of-band by Tower's bootstrap script (which reads # from OpenBao). The chart never sees access/secret keys directly. s3: endpoint: https://s3.eu-central-1.s4.mega.io region: eu-central-1 bucket: odoosky-v3-backups # Per-instance S3 key prefix. Each instance writes under its own # code/ subdirectory inside the shared bucket. prefix: "{{ .Values.instance.code }}" # Name of the K8s Secret holding AWS_ACCESS_KEY_ID + # AWS_SECRET_ACCESS_KEY. Mounted via envFrom on the backup Job. credentialsSecret: s3-backup-creds ingress: # Traefik entrypoint name (set on the Traefik install in the # `traefik` namespace). entryPoint: websecure # The pre-provisioned wildcard cert for *.tenants.odoosky.org — # one Certificate resource issued ONCE in the chart's release # namespace, then every instance's IngressRoute references the # resulting Secret. Avoids Let's Encrypt's per-week certificate # issuance ceiling (50/week/registered-domain) as we scale to # many tenants. # # See infrastructure/cluster/wildcard-cert.yaml for the # provisioning manifest. tlsSecret: tenants-wildcard-tls # useTenantsDefaults — when true, the IngressRoute references the # cluster-level `tenants-default-retry` Middleware (rendered by the # cluster-platform-v3 chart in the same `tenants` namespace). # Default true matches the standard platform shape; flip to false # only when running on a cluster whose Traefik install isn't # paired with the platform's defaults Middleware set (e.g. an # externally-managed Traefik, or cluster-platform-v3's # `traefik.enabled` is false). Audit B.11. useTenantsDefaults: true