Skip to content

Getting started with the management plane

This guide takes you from an empty kind cluster to a sign-in to Console. Console then lists one orchestration cluster. The management plane is a CamundaManagementCluster. It runs Management Identity, Console, and a Keycloak that the operator creates for you. Management Identity is the identity system that Console, Web Modeler, and Optimize sign in through. Camunda explains it in Management Identity.

The guide follows one path. CloudNativePG runs the PostgreSQL server for the databases. You reach each component through a port forward, not an Ingress. The sizes fit a local kind cluster. They are not production sizes.

The Management plane guide covers the other identity providers, Web Modeler, and the routing for a real domain.

The ready-made manifests

The manifests on this page come from config/example/camunda-management-cluster/keycloak. The link shows the main branch. Select the tag of your release on GitHub to get the manifests of that version.

That directory is written for a real domain, so this page changes some values. The changes are:

File On this page
02-secrets.yaml Not used. The page sets no license key and runs no Web Modeler, so it needs neither Secret.
05-platform-config.yaml No licenseSecretRef.
06-management-cluster.yaml The Keycloak externalUrl is the address of the Keycloak Service inside the cluster, http://my-management-keycloak-service.my-management-ns.svc:8080/auth. The externalUrl of Management Identity and of Console is a localhost address of a port forward. No webModeler block.
08-camunda-cluster.yaml externalUrl is http://localhost:8088.
09-optimize.yaml An optional last step. externalUrl is http://localhost:8090.

To apply the directory in one command instead, clone the repository at your release tag. Replace the placeholders in 02-secrets.yaml, and route each camunda.example.com URL to its Service. The README of the directory lists these steps. Then run this command after step 3 of this page:

kubectl apply -k config/example/camunda-management-cluster/keycloak

Before you start

You need:

  • kubectl, helm 3.8 or later, and kind
  • a Bash shell. The commands on this page use Bash syntax. On Windows, use WSL or Git Bash.
  • Docker or Podman, which runs the kind node
  • about 10 GB of free memory and 4 CPUs for the kind node. This is an estimate from the requests of the pods, not a measured value.
  • about 40Gi of free disk for the volumes: 16Gi for the Zeebe broker, 16Gi for Elasticsearch, and 8Gi for PostgreSQL
  • a node that can pull images from Docker Hub, docker.elastic.co, ghcr.io, and quay.io

Create the kind cluster. Its default StorageClass standard binds the volumes of this guide.

kind create cluster --name camunda
kubectl version

The server version must be 1.34 or later. See the requirements. If it is earlier, delete the cluster and create it again with a newer node image:

kind delete cluster --name camunda
kind create cluster --name camunda --image kindest/node:v1.34.0

Elasticsearch needs vm.max_map_count of at least 262144 in the kernel that runs the kind node. If Docker or Podman runs directly on a Linux host, set it on the host:

sudo sysctl -w vm.max_map_count=262144

If your container runtime runs in a virtual machine, set the value in the kind node instead. Docker Desktop does so on every system, and so does a Podman machine. Set the value again after the virtual machine restarts:

docker exec camunda-control-plane sysctl -w vm.max_map_count=262144

With Podman, use podman exec.

Each step shows a manifest. Save it to a file and apply it with kubectl apply -f <file>.

1. Create the namespaces

The management plane runs in my-management-ns. The orchestration cluster runs in my-cluster-ns.

apiVersion: v1
kind: Namespace
metadata:
  name: my-management-ns
---
apiVersion: v1
kind: Namespace
metadata:
  name: my-cluster-ns

Create them first, because the Keycloak Operator in step 2 runs in my-management-ns.

2. Install the operators that this guide needs

The operator runs Elasticsearch through ECK, PostgreSQL through CloudNativePG, and Keycloak through the Keycloak Operator. Install all three before the operator. The operator looks for their CRDs when it starts. If you install one of them later, restart the operator.

The versions below are the ones that the end-to-end suite of each release runs.

Install ECK 3.5.0:

kubectl apply --server-side -f https://download.elastic.co/downloads/eck/3.5.0/crds.yaml
kubectl apply --server-side -f https://download.elastic.co/downloads/eck/3.5.0/operator.yaml

Install CloudNativePG 1.30.1:

kubectl apply --server-side -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.1.yaml
kubectl rollout status deployment/cnpg-controller-manager -n cnpg-system

This guide needs no Barman Cloud plugin and no cert-manager. They are only for a PostgreSQL server that archives to a bucket.

Install the Keycloak Operator 26.6.4. Its CRDs are cluster-wide. The Keycloak Operator itself runs Keycloak only in its own namespace, so install it into my-management-ns:

KC=https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.6.4/kubernetes
kubectl apply --server-side -f $KC/keycloaks.k8s.keycloak.org-v1.yml
kubectl apply --server-side -f $KC/keycloakrealmimports.k8s.keycloak.org-v1.yml
kubectl apply --server-side -n my-management-ns -f $KC/kubernetes.yml
kubectl rollout status deployment/keycloak-operator -n my-management-ns

The Keycloak Operator release must match the Keycloak version of step 6. With Camunda 8.9, keep that version below 26.7.0. The operator runs Keycloak gives the reason.

Use --server-side for each file above. Some of these files carry a CRD that is larger than the annotation that client-side apply writes. Server-side apply works for every file.

3. Install the operator

helm install camunda-operator \
  oci://ghcr.io/konsole-is/charts/camunda-operator \
  --version <version> \
  --namespace camunda-operator-system \
  --create-namespace

Replace <version> with a released version without the v, for example 0.1.0 for the release v0.1.0. Make sure that the manager is running:

kubectl get pods -n camunda-operator-system

4. Create the presets and the release

A preset holds the sizes of a resource. A release holds the versions. The DatabaseServer, the ElasticsearchCluster, and the CamundaCluster in the next steps each name one preset and the release, and set only their own values. All four are cluster-scoped. The presets guide explains both kinds.

apiVersion: core.camunda.io/v1
kind: CamundaClusterPreset
metadata:
  name: small
spec:
  cluster:
    zeebe:
      replicas: 1
      partitions: 1
      replicationFactor: 1
      storageSize: 16Gi
      resources:
        requests:
          cpu: "1"
          memory: 1.5Gi
    gateway:
      resources:
        requests:
          cpu: 500m
          memory: 512Mi
---
apiVersion: core.camunda.io/v1
kind: ElasticsearchClusterPreset
metadata:
  name: standard
spec:
  cluster:
    replicas: 1
    storageSize: 16Gi
    resources:
      requests:
        cpu: 500m
        memory: 1Gi
---
apiVersion: core.camunda.io/v1
kind: DatabaseServerPreset
metadata:
  name: standard
spec:
  server:
    instances: 1
    storageSize: 8Gi
    resources:
      requests:
        cpu: 250m
        memory: 512Mi
---
apiVersion: core.camunda.io/v1
kind: CamundaRelease
metadata:
  name: camunda-8-9
spec:
  version: "8.9.23"
  connectors:
    version: "8.9.14"
  elasticsearch:
    version: "9.2.8"
  databaseServer:
    version: "17"

The same manifests are in config/example/presets and config/example/releases.

5. Create the databases

Keycloak, Management Identity, and Web Modeler each need a PostgreSQL database of their own. A DatabaseServer runs the PostgreSQL server. The field databaseServerConfig names the contract that the server publishes, here my-db-server.

apiVersion: core.camunda.io/v1
kind: DatabaseServer
metadata:
  name: my-db
  namespace: my-management-ns
spec:
  presetRef: standard
  releaseRef: camunda-8-9
  databaseServerConfig: my-db-server

Wait until it is ready. The first start pulls the PostgreSQL image.

kubectl wait databaseserver/my-db -n my-management-ns \
  --for=condition=Ready --timeout=10m

Then create one Database per component. Each one creates a DatabaseConfig of the same name, which the management plane names in step 6.

apiVersion: core.camunda.io/v1
kind: Database
metadata:
  name: my-keycloak-db
  namespace: my-management-ns
spec:
  serverRef: my-db-server
  databaseName: keycloak
---
apiVersion: core.camunda.io/v1
kind: Database
metadata:
  name: my-identity-db
  namespace: my-management-ns
spec:
  serverRef: my-db-server
  databaseName: identity
---
apiVersion: core.camunda.io/v1
kind: Database
metadata:
  name: my-web-modeler-db
  namespace: my-management-ns
spec:
  serverRef: my-db-server
  databaseName: web_modeler

This guide runs no Web Modeler, so nothing uses my-web-modeler-db yet. It is there for the moment you add Web Modeler.

6. Create the management plane

A CamundaPlatformConfig holds the settings that all clusters share. This one selects basic authentication for the orchestration cluster. That choice is separate from the sign-in to Console, which goes through Keycloak.

apiVersion: core.camunda.io/v1
kind: CamundaPlatformConfig
metadata:
  name: my-platform-config
spec:
  auth:
    method: basic

This configuration sets no license key, because it has no licenseSecretRef. CamundaPlatformConfig is cluster-scoped. It has no namespace.

Then create the management plane:

apiVersion: core.camunda.io/v1
kind: CamundaManagementCluster
metadata:
  name: my-management
  namespace: my-management-ns
spec:
  platformConfigRef: my-platform-config
  clusterSelector:
    matchLabels:
      environment: production
  identityProvider:
    keycloak:
      version: "26.6.4"
      externalUrl: "http://my-management-keycloak-service.my-management-ns.svc:8080/auth"
      databaseConfigRef: my-keycloak-db
  identity:
    version: "8.9.10"
    externalUrl: "http://localhost:8084"
    databaseConfigRef: my-identity-db
    admin:
      username: admin
      email: admin@example.com
  console:
    version: "8.9.122"
    externalUrl: "http://localhost:8087"

Each externalUrl is the address that your browser opens. Step 8 forwards each of these ports to its Service.

The Keycloak externalUrl is different. The pods of the management plane reach Keycloak at this address too, so it is the address of the Keycloak Service inside the cluster. Its path must be exactly /auth. Step 8 makes your browser reach the same address.

clusterSelector selects the orchestration clusters that Console lists. This one selects every CamundaCluster with the label environment: production.

Wait until it is ready. The first start pulls the images of Keycloak, Management Identity, and Console, and can take a few minutes.

kubectl wait camundamanagementcluster/my-management -n my-management-ns \
  --for=condition=Ready --timeout=15m
kubectl get camundamanagementcluster -n my-management-ns
NAME            READY   REASON    AGE
my-management   True    Healthy   9m

If Ready stays False, the reason names the problem. Status says what to do for each reason. For example, KeycloakOperatorNotInstalled means that the operator started before the Keycloak CRDs existed. Restart the operator.

7. Create the orchestration cluster

The orchestration cluster stores its data in Elasticsearch. Create the ElasticsearchCluster first. It publishes the contract my-storage-config, which the cluster names in storageRef.

apiVersion: core.camunda.io/v1
kind: ElasticsearchCluster
metadata:
  name: my-cluster-es
  namespace: my-cluster-ns
spec:
  presetRef: standard
  releaseRef: camunda-8-9
  secondaryStorageConfig: my-storage-config

Then the cluster. The label environment: production is what the clusterSelector of step 6 matches.

apiVersion: core.camunda.io/v1
kind: CamundaCluster
metadata:
  name: my-cluster
  namespace: my-cluster-ns
  labels:
    environment: production
spec:
  presetRef: small
  releaseRef: camunda-8-9
  platformConfigRef: my-platform-config
  storageRef: my-storage-config
  externalUrl: "http://localhost:8088"

Wait until it is ready. The cluster waits for Elasticsearch, and the first start pulls both images.

kubectl wait camundacluster/my-cluster -n my-cluster-ns \
  --for=condition=Ready --timeout=15m

Then wait until the management plane serves the cluster. It attaches the cluster a short time after the cluster is ready:

kubectl wait camundamanagementcluster/my-management -n my-management-ns \
  --for=jsonpath='{.status.clusters[?(@.name=="my-cluster")].attached}'=true --timeout=5m
kubectl get camundamanagementcluster my-management -n my-management-ns \
  -o jsonpath='{range .status.clusters[*]}{.name}{"\t"}{.attached}{"\t"}{.reason}{"\n"}{end}'
my-cluster  true

If the wait ends without success, the line shows false and a reason. Clusters lists the other reasons.

8. Sign in to Console

The operator creates the first administrator, admin. The Secret my-management-identity-admin holds the password:

kubectl get secret my-management-identity-admin -n my-management-ns \
  -o go-template='{{.data.password | base64decode}}'

Your browser must reach Keycloak at its externalUrl from step 6. Add this line to the hosts file of your computer. The file is /etc/hosts on Linux and macOS, and C:\Windows\System32\drivers\etc\hosts on Windows. Edit it as an administrator.

127.0.0.1 my-management-keycloak-service.my-management-ns.svc

Forward the ports of Keycloak, Management Identity, and Console. Each command runs until you stop it, so run each one in a terminal of its own:

kubectl port-forward svc/my-management-keycloak-service -n my-management-ns 8080:8080
kubectl port-forward svc/my-management-identity -n my-management-ns 8084:80
kubectl port-forward svc/my-management-console -n my-management-ns 8087:80

The local ports are the ports in the externalUrl fields of step 6. If you change the local port of Management Identity or Console, change its externalUrl too. Keep Keycloak on local port 8080, because its externalUrl names that port.

Open http://localhost:8087. Console sends you to the Keycloak sign-in page. Sign in as admin with the password above. Console lists my-cluster. The cluster restarts its pods once after it attaches, and it reports to Console when it starts again. If the list is empty, wait one minute and reload the page. Camunda marks the cluster list of Console as experimental in 8.9, under experimental features.

Management Identity is at http://localhost:8084. Sign in there with the same user.

To open Operate on the cluster, forward the gateway port in another terminal. Local port 8080 is in use by Keycloak, so this guide uses 8088, the port of the cluster externalUrl in step 7:

kubectl port-forward svc/my-cluster-gateway -n my-cluster-ns 8088:8080

Open http://localhost:8088/operate/.

Operate uses the basic authentication of the cluster, not Keycloak. Its user and password are not the ones of Management Identity. The username is admin, and the Secret my-cluster-camunda-admin holds the password:

kubectl get secret my-cluster-camunda-admin -n my-cluster-ns \
  -o go-template='{{.data.password | base64decode}}'

9. Optional: add Optimize

Optimize reads the data of one orchestration cluster and signs in through the management plane. It needs more memory on top of the sizes in Before you start.

apiVersion: core.camunda.io/v1
kind: CamundaOptimize
metadata:
  name: my-cluster-optimize
  namespace: my-cluster-ns
spec:
  version: "8.9.23"
  managementAuthRef: my-management
  externalUrl: "http://localhost:8090"
  clusterRef:
    name: my-cluster

managementAuthRef names the contract that my-management publishes. Wait until it is ready:

kubectl wait camundaoptimize/my-cluster-optimize -n my-cluster-ns \
  --for=condition=Ready --timeout=15m

The management plane also registers the sign-in address of Optimize in Keycloak and gives admin the Optimize role. Until it has done so, the sign-in to Optimize fails. The condition OptimizeCallbacksReady reads True with the reason NoCallbacks before any Optimize exists, so wait for the reason Healthy:

kubectl wait camundamanagementcluster/my-management -n my-management-ns \
  --for=jsonpath='{.status.conditions[?(@.type=="OptimizeCallbacksReady")].reason}'=Healthy \
  --timeout=5m

Then forward the port of Optimize. The command runs until you stop it, so run it in a terminal of its own:

kubectl port-forward svc/my-cluster-optimize-webapp -n my-cluster-ns 8090:8090

Open http://localhost:8090 and sign in as admin. The Keycloak port forward and the hosts file line of step 8 must stay in place. See CamundaOptimize for the rest.

This local setup has one limit for Optimize. The Optimize pod calls Management Identity at its externalUrl, http://localhost:8084, and that address does not answer inside the cluster. The parts of Optimize that read users or tenants from Management Identity can fail. The limit goes away when the externalUrl of Management Identity also answers inside the cluster, as a real domain usually does.

Web Modeler needs an SMTP server, so this guide leaves it out. Step 3d of the Management plane guide adds it.

10. Clean up

kubectl delete camundaoptimize my-cluster-optimize -n my-cluster-ns --ignore-not-found
kubectl delete camundacluster my-cluster -n my-cluster-ns
kubectl delete elasticsearchcluster my-cluster-es -n my-cluster-ns
kubectl delete camundamanagementcluster my-management -n my-management-ns
kubectl delete database --all -n my-management-ns
kubectl delete databaseserver my-db -n my-management-ns
kubectl delete camundaplatformconfig my-platform-config
kubectl delete namespace my-management-ns my-cluster-ns

Deleting the namespace my-management-ns also removes the Keycloak Operator. The presets and the release stay. Delete them when nothing else uses them:

kubectl delete camundaclusterpreset small
kubectl delete elasticsearchclusterpreset standard
kubectl delete databaseserverpreset standard
kubectl delete camundarelease camunda-8-9

To remove the whole Kubernetes cluster at once, delete the kind cluster instead:

kind delete cluster --name camunda

Neither way removes the line that you added to the hosts file in step 8. Remove the line with my-management-keycloak-service.my-management-ns.svc yourself. The file is /etc/hosts on Linux and macOS, and C:\Windows\System32\drivers\etc\hosts on Windows.

Next steps

  • Management plane: run the management plane on your own domain, with your own Keycloak or OIDC provider, and with Web Modeler.
  • CamundaManagementCluster reference: every condition and generated Secret, with a link to every field.
  • Presets: write the sizes once and create each cluster in a few lines.
  • Getting started: an orchestration cluster alone, without a management plane.