The cmp chart installs a complete Gentics Content Management Platform into a
single Kubernetes namespace with one helm install. It is an
umbrella chart: it does not contain the components themselves, it depends
on the individual product charts and adds the pieces that only make sense when they are
deployed together — a bundled MariaDB, a shared license secret, the database bootstrap job
and cross-component wiring.
If you deploy the components individually (separate releases, separate lifecycles, external database), use the per-component charts instead — see Kubernetes installation of the individual charts.
What the chart deploys
| Component | Values section | Chart | Enabled by default |
|---|---|---|---|
| Gentics CMS | cms | cms | yes |
| Gentics Mesh | mesh | gentics-mesh | yes |
| Gentics Portal | Java | portal-java | gentics-portal-java | no |
| Gentics Portal | PHP | portal-php | gentics-portal-php | no |
| MariaDB (bundled/or external) | mariadb | (templates of this chart) | yes (global.mariadb.enabled) |
Each component is switched on and off with <section>.enabled. The
switch is a Helm dependency condition, so a disabled component renders nothing
at all.
In addition to the component charts, the umbrella chart itself renders:
| Resource | Name | Purpose |
|---|---|---|
| StatefulSet, Service, PVC, ConfigMap, Secret | <release>-mariadb |
bundled MariaDB (single instance) |
| Secret | <release>-license |
shared CMP license key (only when global.license.key is set) |
| Secret | <release>-cms-db, <release>-mesh-db |
generated database passwords for CMS and Mesh |
Job (post-install hook) |
<release>-db-init |
creates the cms and mesh databases, users and grants |
Both portal charts have no pre-built image — you build your own portal image and reference it. The single exception is the example installation, which ships a ready-made reference portal.
Prerequisites
- Kubernetes 1.23 or newer. The chart declares
kubeVersion: ">=1.23.0-0"; Helm refuses to install on older clusters. - Helm 3. Several templates use
lookup, so generated passwords survive upgrades. - A namespace for the installation. One CMP release per namespace is the recommended layout.
- An image pull secret for
docker.gentics.com, created in that namespace. Your Gentics technical customer consultant provides the credentials. The chart references it throughglobal.imagePullSecrets(default:docker-gentics-com). - A CMP license key, also provided by your consultant.
- A default StorageClass, or explicit
storageClassvalues per volume. The chart requestsReadWriteOncevolumes for MariaDB and CMS data andReadWriteManyfor the Mesh shared volume (uploads, keystore) — the latter needs a shared filesystem (typically NFS) as soon as Mesh runs with more than one replica. - The Gentics Helm repository:
helm repo add --username USERNAME --password PASSWORD \
gentics https://repo.gentics.com/repository/helm/
helm repo update
Optional, depending on what you enable: Elasticsearch (Mesh search, Content RAG), Keycloak (Portal authentication), an external MariaDB.
Installation
Create the pull secret and inspect the defaults:
kubectl -n NAMESPACE create secret docker-registry docker-gentics-com \
--docker-server=docker.gentics.com \
--docker-username=USERNAME --docker-password=PASSWORD
helm show values gentics/cmp > my-cmp-values.yaml
Keep your own my-cmp-values.yaml under version control and install with
it:
helm -n NAMESPACE upgrade --install -f my-cmp-values.yaml cmp gentics/cmp
A minimal CMS + Mesh + MariaDB installation needs nothing but the license key:
helm -n NAMESPACE upgrade --install cmp gentics/cmp \
--set global.license.key='YOUR-LICENSE-KEY'
cmp is the release name and prefixes every resource, so keep it short. All
examples below assume it.
Do not add --wait to the first install. The database
bootstrap runs as a post-install hook, and Helm runs post-install hooks only
after its readiness wait — while CMS and Mesh cannot become ready before their
databases exist. --wait would therefore time out on a fresh install. Watch the
rollout with kubectl get pods -w instead.
To pin the chart version, add e.g. --version 8.4.5. The component images
follow the appVersion explicitly, and the release process keeps those versions
aligned with the CMP version.
Configuration
Values are organised in three groups:
1. global.* — settings shared by all components.
| Value | Default | Effect |
|---|---|---|
global.imageRegistry |
"" |
Replaces the registry of every image, including the Docker Hub ones (MariaDB, spellchecker, the API-key job's curl image). Your mirror must proxy those too. |
global.imagePullSecrets |
[{name: docker-gentics-com}] |
Pull secrets for all component pods. |
global.license.key |
"" |
Creates <release>-license; CMS, Mesh and Portal | Java read the
key from it. |
global.mariadb.enabled |
true |
Deploys the bundled MariaDB and wires CMS and Mesh to it. |
global.mesh.enabled |
true |
Tells the portals that this release deploys Mesh, so their connection is
auto-wired. Must match mesh.enabled. |
global.persistence.keepOnUninstall |
true |
Whether PVCs survive helm uninstall and Argo CD pruning. |
global.example.enabled |
false |
Example installation. |
2. <component>.* — everything the component chart itself
offers. The keys under cms, mesh,
portal-java and portal-php are passed to the respective sub-chart
unchanged and merged with its own defaults, so anything the component chart supports works
here too. The values file of this chart only carries the handful of keys that need a
CMP-specific default; for the full list of options ask the component chart:
helm show values gentics/cms
helm show values gentics/gentics-mesh
helm show values gentics/gentics-portal-java
helm show values gentics/gentics-portal-php
3. mariadb.* — the bundled MariaDB (image, root password,
persistence, service, resources, probes, config). Only evaluated while
global.mariadb.enabled is true.
Precedence: a value set inside a component section always wins over the corresponding
global.* value. Note the naming: the sub-charts are aliased, so the Mesh
section is mesh (not gentics-mesh) and the portal sections are
portal-java / portal-php. Because of the dash,
--set needs quoting in most shells:
helm ... --set 'portal-java.enabled=true'
Rendering fails immediately, before anything is applied, when
global.mesh.enabled and mesh.enabled disagree. Check any change
without touching the cluster:
helm -n NAMESPACE template cmp gentics/cmp -f my-cmp-values.yaml | less
Database
Bundled MariaDB
global.mariadb.enabled: true
deploys a single MariaDB instance and does the wiring for you:
- CMS and Mesh get
<release>-mariadb:3306as their database host. - The
<release>-db-initpost-install job creates databasecmswith usernodeand databasemeshwith usermesh, using the passwords from<release>-cms-dband<release>-mesh-db. - The root password is taken from
mariadb.auth.rootPassword, or generated once and persisted in<release>-mariadb.
The bundled instance is a single pod with a ReadWriteOnce volume and no
replication — convenient for evaluation, test stages and small installations. For
production, weigh it against a managed or clustered database.
Database names and users can be changed via cms.database.name /
cms.database.username and mesh.database.name /
mesh.database.username; the init job picks them up.
The init job is a post-install hook — it does not run on
upgrades. If you enable a component later (e.g.
mesh.enabled: false → true on an existing release), its database,
user and grants are not created for you. Create them in the bundled MariaDB by hand, using
the password from the corresponding Secret:
kubectl -n NAMESPACE exec -it cmp-mariadb-0 -- mariadb -u root -p
External database
Set global.mariadb.enabled: false and
configure both components explicitly. Nothing is created or bootstrapped for you —
database, users and grants have to exist:
global:
mariadb:
enabled: false
cms:
database:
host: db.example.com
port: "3306"
name: cms
username: node
passwordSecret: cms-db-secret # Secret with key "password"
mesh:
database:
host: db.example.com
port: 3306
name: mesh
username: mesh
passwordSecret: mesh-db-secret # Secret with key "password"
Instead of passwordSecret you may set password directly; the
chart then creates the Secret from the value. Missing values are reported as
database.host is required when global.mariadb.enabled is false at render
time.
Generated credentials
Where a password is not configured, it is generated on first install, stored in a Secret and reused on every later upgrade (the templates read the existing Secret back). Read them out with:
# CMS "node" user
kubectl -n NAMESPACE get secret cmp-cms \
-o jsonpath='{.data.node-user-password}' | base64 -d; echo
# Mesh "admin" user
kubectl -n NAMESPACE get secret cmp-mesh-admin \
-o jsonpath='{.data.password}' | base64 -d; echo
# MariaDB root
kubectl -n NAMESPACE get secret cmp-mariadb \
-o jsonpath='{.data.root-password}' | base64 -d; echo
To set them yourself: cms.nodeUserPassword /
cms.nodeUserPasswordSecret,
mesh.credentials.initialAdminPassword /
mesh.credentials.existingSecret, mariadb.auth.rootPassword /
mariadb.auth.existingSecret.
Mesh API key for the portals
Both portals authenticate against Mesh with an API key. The Mesh chart can issue it itself, which is the intended path under the umbrella:
mesh:
generateApiKey: true
A Job waits for Mesh to become ready, logs in as admin and patches the token
into the Secret <release>-mesh-secret (key apikey) — exactly
the Secret the portals read by default. Details that matter in operation:
- The (empty) Secret is always rendered, so switching the flag off later removes only the Job; the stored key stays.
- Create-once: an existing key is never regenerated, because Mesh invalidates the previous token whenever a new one is issued.
- Issuing the token invalidates any other API token of that Mesh user.
- Forced regeneration:
kubectl -n NAMESPACE delete secret cmp-mesh-secret, then runhelm upgrade(or an Argo CD sync) with the flag enabled. - A portal pod that starts before the key exists stays in
CreateContainerConfigErroruntil the Job has patched it in. That resolves itself.
Alternatives: point the portal at a Secret you manage
(portal-java.mesh.secretName, portal-php.mesh.secretName), or pass
the key inline (portal-java.mesh.apiKeyFromSecret: false plus
portal-java.mesh.apikey, portal-php.mesh.apikey).
Exposing the services
All Charts have the option to create a Ingress. The Ingress is disabled by default so an installation is only reachable within the cluster. You can use the built in Ingress or you create Ingress / VirtualServer on your own
E.g for CMS a ingress can be enabled in the values.yaml
cms:
cms:
ingress:
enabled: true
host: cms.example.com
tls: true
secretName: cms-example-com-tls
Without an ingress, reach a component through a port-forward:
kubectl -n NAMESPACE port-forward svc/cmp-cms 8080:80
Persistence and Uninstall
All PVCs created by the charts carry, by default:
helm.sh/resource-policy: keep
argocd.argoproj.io/sync-options: Delete=false
so helm uninstall and Argo CD pruning leave the data behind, and reinstalling
with the same release name picks the old volumes up again. An accidental uninstall is
therefore never a data-loss event.
For throwaway installations, turn that off — either globally or per component:
global:
persistence:
keepOnUninstall: false # all components
mariadb:
persistence:
keepOnUninstall: true # ... except this one
Precedence, highest first: the component's own
persistence.keepOnUninstall → global.example.enabled: true (forces
"delete", see Example installation) →
global.persistence.keepOnUninstall → keep.
Removing volumes regardless of the flags:
kubectl -n NAMESPACE delete pvc -l app.kubernetes.io/instance=cmp
Example installation
The example installation deploys a ready-made reference project instead of requiring you to build and supply your own portal image. It is meant for a first look at a complete CMP — CMS, Mesh, MariaDB and a working Portal | Java in front of them — not as a starting point for a real project.
helm -n NAMESPACE upgrade --install cmp gentics/cmp \
--set global.example.enabled=true \
--set 'portal-java.enabled=true' \
--set global.license.key='YOUR-LICENSE-KEY' \
--set 'portal-java.portal.server.url=https://portal.example.com' \
--set mesh.generateApiKey=true
The same as a values file:
global:
example:
enabled: true
license:
key: "YOUR-LICENSE-KEY"
mesh:
generateApiKey: true
ingress:
enabled: true
hosts:
- host: mesh.example.com
paths: ["/"]
portal-java:
enabled: true
portal:
server:
url: https://portal.example.com
ingress:
enabled: true
host: portal.example.com
What happens when global.example.enabled is true:
global.example.portalJavaImagereplacesportal-java.imageentirely — anything you configure underportal-java.imageis ignored, and the otherwise mandatoryportal-java.image.repositoryis not required.- The reference portal receives the extra environment from
global.example.env:INIT_EXAMPLE=true, the in-cluster CMS URL, the CMSnodepassword and the Mesh admin password from their Secrets, plusMESH_SCHEMA_PREFIX=exampleandMESH_PROJECT=Example. On first start the portal image uses these to create the demo project in Mesh and the matching content in the CMS. - Because the portal needs the CMS and Mesh credentials, the example only works together
with the components of the same release (
cms.enabledandmesh.enabled, both default). - Persistent volumes are no longer marked to be kept. The example is a
throwaway demo, so the PVC keep annotations are dropped and
helm uninstalltakes its volumes with it. This overridesglobal.persistence.keepOnUninstall— setting that totruedoes not bring the annotations back. A per-component<component>.persistence.keepOnUninstall: truestill wins, if you really want to keep one volume (e.g.mariadb.persistence.keepOnUninstall: true).
Four values are required, and the chart checks them all at once — a single failed
helm install reports the complete list rather than one problem per attempt:
Watch the rollout and retrieve the credentials for the CMS and Mesh backends:
kubectl -n NAMESPACE get pods -w
kubectl -n NAMESPACE get secret cmp-cms \
-o jsonpath='{.data.node-user-password}' | base64 -d; echo
kubectl -n NAMESPACE get secret cmp-mesh-admin \
-o jsonpath='{.data.password}' | base64 -d; echo
To remove the demo again:
helm -n NAMESPACE uninstall cmp
Its volumes go with it (see above), so nothing is left behind.
Upgrading
helm repo update
helm -n NAMESPACE upgrade -f my-cmp-values.yaml cmp gentics/cmp --version 8.5.0
- Always upgrade with your values file (or
--reuse-values); a barehelm upgradefalls back to the chart defaults. - Review the changelog before a version jump.
- Pin your version
--version 8.5.0, this avoids unintentional version upgrades - Generated passwords are read back from their Secrets and stay stable across upgrades.
- Component image tags follow the component chart's
appVersion, so a chart upgrade moves the images too — unless you pinned<section>.image.tag. helm upgrade --installis safe to use for the first install as well; that is what the--installflag is for.
Uninstalling
helm -n NAMESPACE uninstall cmp
PVCs (and therefore CMS data, Mesh uploads and the database) remain unless
keepOnUninstall was set to false; see
Persistence and uninstall. Secrets created by the
chart are removed with the release — copy out any password you still need before
uninstalling.
GitOps / Argo CD
Two properties of the chart matter for a GitOps setup:
- Generated passwords rely on
lookup. Argo CD renders manifests without cluster access in some configurations, and a render withoutlookupwould produce a new random password. Reference pre-created Secrets instead, so nothing is generated:mariadb.auth.existingSecret,mesh.credentials.existingSecret,cms.nodeUserPasswordSecret,cms.database.passwordSecret,mesh.database.passwordSecret,global.license.keyreplaced by per-componentlicense.secret. - The Mesh API-key Job is a plain Job, not a Helm hook — deliberately,
because post-install hooks deadlock with
helm --waitand with Argo CD (both wait for the consumers to become healthy while the consumers wait for the key). It runs alongside the other resources and the portal pods start as soon as the key is patched in.
The PVC annotation argocd.argoproj.io/sync-options: Delete=false keeps
volumes out of Argo CD's pruning; see
Persistence and uninstall.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
Inconsistent values: global.mesh.enabled is true but mesh.enabled is false |
Either enable the bundled Mesh, or set global.mesh.enabled=false and
point the portals at your external Mesh
(portal-java.mesh.host/port,
portal-php.mesh.url). |
The example installation ... cannot be installed — N value(s) missing |
The list in the message is complete; see Example installation. |
database.host is required when global.mariadb.enabled is false |
Configure cms.database.* / mesh.database.*, or re-enable
the bundled MariaDB. |
portal-java has no pre-built image |
Set portal-java.image.repository (and image.tag) to your
own portal image, or use the example installation. |
Portal pod in CreateContainerConfigError |
The Mesh API key is not in <release>-mesh-secret yet. Check the
<release>-mesh-apikey-* Job; see
Mesh API key. |
Pods in ImagePullBackOff |
Pull secret missing or wrong in the namespace, or global.imageRegistry
set to a mirror that does not proxy Docker Hub or does not have access to docker.gentics.com |
| CMS or Mesh pod cannot reach the database | Check the <release>-db-init Job — it creates databases, users and
grants and retries until MariaDB answers. |
PVCs stay behind after helm uninstall |
By design, see Persistence and uninstall. |
Useful commands:
kubectl -n NAMESPACE get pods,svc,pvc,jobs
kubectl -n NAMESPACE logs job/cmp-db-init
kubectl -n NAMESPACE logs -l app.kubernetes.io/name=mesh-apikey
kubectl -n NAMESPACE describe pod POD # events at the bottom
helm -n NAMESPACE get values cmp # what the release actually runs with
helm -n NAMESPACE history cmp
