Quickstart

This quickstart provides the steps to install the Kubermatic Developer Platform (KDP) on an existing Kubernetes cluster. You’ll use Helm to deploy KDP and its core components, including Dex for user authentication and kcp as central control plane. You will also set up automated TLS certificate management with cert-manager and Let’s Encrypt. By the end, you will have a fully functional KDP installation, accessible through the KDP dashboard as well as directly with kubectl.

Prerequisites

At the moment, you need to be invited to get access to Kubermatic’s Docker repository before you can install the Kubermatic Developer Platform. Please contact sales to receive your credentials.

To follow this guide, you need:

Installation

The installation is divided into six main steps, each deploying a core component of KDP. You will perform the following tasks:

  • Set up certificates: First, you will configure a cert-manager issuer to automatically obtain and renew TLS certificates from Let’s Encrypt.

  • Deploy an identity provider: Next, you will deploy Dex to handle user authentication, creating a central login service for both the KDP dashboard and command-line access.

  • Deploy kcp: You will deploy kcp, the core engine that enables multi-tenancy by providing isolated, secure workspaces for your users.

  • Deploy KDP: Afterwards, you will install the main KDP controllers that connect to kcp and manage the platform’s resources.

  • Launch the KDP dashboard: You will deploy the KDP dashboard, the primary graphical interface for developers to interact with the platform and manage their service objects.

  • Deploy the KDP AI Agent: Finally, you will deploy the AI Agent, which provides AI-powered features within the dashboard — generating Kubernetes resource specs and customizing resource creation forms from natural language prompts.

Throughout this guide, you will need to replace several placeholder variables in the Helm values files. Below is a description of each value you need to provide.

  • <EMAIL_ADDRESS>: Your email address, used by Let’s Encrypt to send notifications about your TLS certificate status.
  • <PULL_CREDENTIALS>: A base64-encoded password or token for the quay.io container registry. This is required for you to get access to the KDP Helm charts and container images.
  • <DOMAIN>: The primary public domain name you will use to access your KDP installation (e.g., kdp.my-company.com). You must own this domain and be able to configure its DNS records.
  • <ADMIN_PASSWORD_HASH>: A generated bcrypt hash of the password you choose for the initial admin user.
  • <OIDC_CLIENT_SECRET>: A randomly generated, secure string that acts as a password for the KDP dashboard to authenticate with the Dex identity provider.
  • <SESSION_ENCRYPTION_KEY>: A second, unique random string used by the KDP dashboard itself to encrypt user session cookies, adding another layer of security.
  • <OPENAI_API_KEY>: An API key from OpenAI, required by the KDP AI Agent for its AI-powered features (spec generation and UI schema generation).

Create ClusterIssuer

First, you need to create a ClusterIssuer named letsencrypt-prod for cert-manager. This automates the process of obtaining and renewing TLS certificates from Let’s Encrypt, ensuring all web-facing components like the Dex login page and the KDP dashboard are served securely over HTTPS.

Save the following content to a file named cluster-issuer.yaml, and change the value of the email field to your email address:

# cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: <EMAIL_ADDRESS>
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod-credentials
    solvers:
      - http01:
          ingress:
            class: nginx

Create the ClusterIssuer by applying the manifest:

kubectl apply -f ./cluster-issuer.yaml

Gateway API alternative: If you use a Gateway API controller (e.g. [Envoy Gateway][envoy-gateway/docs] or [Contour][contour/docs]) instead of NGINX Ingress, you need to make three changes:

1. Enable Gateway API support in cert-manager. When installing cert-manager, set enableGatewayAPI: true in its controller configuration so it can manage TLS certificates for Gateway listeners:

# cert-manager Helm values
config:
  enableGatewayAPI: true

2. Replace the http01 solver with a gatewayHTTPRoute solver in the ClusterIssuer, pointing to your Gateway:

solvers:
  - http01:
      gatewayHTTPRoute:
        parentRefs:
          - name: shared-gateway
            namespace: <GATEWAY_NAMESPACE>
        serviceType: ClusterIP

3. Deploy Dex and the Dashboard without their built-in Ingress resources and create HTTPRoute resources instead. Since neither the Dex nor the KDP Dashboard Helm chart natively creates HTTPRoute resources, you must disable their Ingress and provide the routing yourself.

Disable Ingress in the Dex values:

# dex.values.yaml
ingress:
  enabled: false

Disable Ingress in the KDP Dashboard values:

# kdp-dashboard.values.yaml
dashboard:
  ingress:
    create: false

Then create a Gateway with HTTPS listeners for each hostname, annotated so cert-manager provisions the TLS certificates automatically. Save the following content to a file named gateway.yaml:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gateway
  namespace: &lt;GATEWAY_NAMESPACE&gt;
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  gatewayClassName: &lt;GATEWAY_CLASS&gt;
  listeners:
    - name: https-login
      protocol: HTTPS
      port: 443
      hostname: login.&lt;DOMAIN&gt;
      tls:
        mode: Terminate
        certificateRefs:
          - name: dex-tls-gw
            kind: Secret
      allowedRoutes:
        namespaces:
          from: All
    - name: https-dashboard
      protocol: HTTPS
      port: 443
      hostname: dashboard.&lt;DOMAIN&gt;
      tls:
        mode: Terminate
        certificateRefs:
          - name: dashboard-tls-gw
            kind: Secret
      allowedRoutes:
        namespaces:
          from: All

Replace <GATEWAY_NAMESPACE>, <GATEWAY_CLASS>, and <DOMAIN> with your values, then apply:

kubectl apply -f ./gateway.yaml

Finally, create HTTPRoute resources to route traffic to Dex and the Dashboard. Save the following content to a file named http-routes.yaml:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: dex
  namespace: kdp-system
spec:
  hostnames:
    - login.&lt;DOMAIN&gt;
  parentRefs:
    - name: shared-gateway
      namespace: &lt;GATEWAY_NAMESPACE&gt;
  rules:
    - backendRefs:
        - name: dex
          port: 5556
      matches:
        - path:
            type: PathPrefix
            value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: kdp-dashboard
  namespace: kdp-system
spec:
  hostnames:
    - dashboard.&lt;DOMAIN&gt;
  parentRefs:
    - name: shared-gateway
      namespace: &lt;GATEWAY_NAMESPACE&gt;
  rules:
    - backendRefs:
        - name: kdp-dashboard
          port: 3000
      matches:
        - path:
            type: PathPrefix
            value: /

Replace <GATEWAY_NAMESPACE> and <DOMAIN>, then apply:

kubectl apply -f ./http-routes.yaml

Note that the kcp API (internal.<DOMAIN>) is not routed through the Gateway — it uses its own dedicated LoadBalancer service and does not need an HTTPRoute.

The public API endpoint (api.<DOMAIN>) is handled separately: the KDP chart creates an NGINX Ingress for it by default. If you are fully replacing NGINX Ingress with a Gateway API controller, you should disable the KDP chart’s built-in Ingress (set kdp.frontProxy.publicDomain to empty) and create an additional Gateway HTTPS listener and HTTPRoute for api.<DOMAIN> pointing to kcp-front-proxy:8443, along with a BackendTLSPolicy for the TLS backend connection.

For the DNS records step, point login.<DOMAIN>, api.<DOMAIN>, and dashboard.<DOMAIN> to the Gateway’s load balancer IP instead of the NGINX ingress controller.

Deploy Dex

Now, you’ll deploy Dex as the platform’s central identity provider. It handles all user logins and authentication. The provided configuration creates an initial admin user and prepares Dex for the integration with the KDP dashboard and kubelogin for a seamless user authentication.

Save the following content to a file named dex.values.yaml:

# dex.values.yaml
config:
  issuer: https://login.<DOMAIN>
  storage:
    type: kubernetes
    config:
      inCluster: true
  staticClients:
    - id: kdp-kubelogin
      name: kdp-kubelogin
      secret: <OIDC_CLIENT_SECRET>
      RedirectURIs:
        - http://localhost:8000
        - http://localhost:18000
        - https://dashboard.<DOMAIN>/api/auth/callback/oidc
  enablePasswordDB: true
  staticPasswords:
    - email: admin
      hash: "<ADMIN_PASSWORD_HASH>"
      username: admin
      userID: 08a8684b-db88-4b73-90a9-3cd1661f5466

ingress:
  enabled: true
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  className: nginx
  hosts:
    - host: login.<DOMAIN>
      paths:
        - path: /
          pathType: ImplementationSpecific
  tls:
    - secretName: dex-tls
      hosts:
        - login.<DOMAIN>

Before deploying Dex, you need to replace the following placeholder variables in the dex.values.yaml file with your own values:

  • <DOMAIN>
  • <ADMIN_PASSWORD_HASH>
  • <OIDC_CLIENT_SECRET>

For the initial admin user, you must provide your own password as bcrypt hash in <ADMIN_PASSWORD_HASH>. To create this hash, you can use the htpasswd utility, which is part of the Apache web server tools and available on most Linux distributions (you may need to install a package like “apache2-utils” or “httpd-tools”).

Choose a strong password and run the following command in your terminal, replacing YOUR_PASSWORD with the password you’ve selected:

echo 'YOUR_PASSWORD' | htpasswd -inBC 10 admin | cut -d: -f2

Copy the entire output string (it will start with $2a$ or $2y$) and paste it as the value for <ADMIN_PASSWORD_HASH> in your dex.values.yaml file. Remember to save the plain-text password you chose in a secure location, as you will need it to log in to the KDP dashboard.

The <OIDC_CLIENT_SECRET> placeholder must be replaced with a long, random string that the KDP dashboard and kubelogin use to securely communicate with Dex. You can generate a secure, random string with the following command:

cat /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c32

This will output a random string that you can copy and paste as the value for <OIDC_CLIENT_SECRET>. Save the value for later use when you deploy the KDP dashboard.

Once you’ve replaced all placeholders, deploy the Dex Helm chart:

helm upgrade --install dex dex \
    --repo=https://charts.dexidp.io \
    --version=0.23.0 \
    --create-namespace \
    --namespace=kdp-system \
    --values=dex.values.yaml

Deploy kcp

Next, you’ll install kcp. It acts as the central control plane for KDP that provides and manages the isolated workspaces for each user or team, ensuring resources are kept separate and secure. It’s configured to use Dex for authenticating user requests.

Save the following content to a file named kcp.values.yaml:

# kcp.values.yaml
externalHostname: "internal.<DOMAIN>"
externalPort: "8443"

kcpFrontProxy:
  service:
    type: LoadBalancer
  additionalPathMappings:
    - path: /services/organization/
      backend: https://kdp-virtual-workspaces:6444
      backend_server_ca: /etc/kcp/tls/ca/tls.crt
      proxy_client_cert: /etc/kcp-front-proxy/requestheader-client/tls.crt
      proxy_client_key: /etc/kcp-front-proxy/requestheader-client/tls.key
    - path: /services/service/
      backend: https://kdp-virtual-workspaces:6444
      backend_server_ca: /etc/kcp/tls/ca/tls.crt
      proxy_client_cert: /etc/kcp-front-proxy/requestheader-client/tls.crt
      proxy_client_key: /etc/kcp-front-proxy/requestheader-client/tls.key
  extraFlags:
    - '--cors-allowed-origins=localhost,dashboard.<DOMAIN>$'
    - '--authentication-drop-groups=system:kcp:logical-cluster-admin'

oidc:
  enabled: true
  issuerUrl: https://login.<DOMAIN>
  clientId: kdp-kubelogin
  groupClaim: groups
  usernameClaim: email
  usernamePrefix: 'oidc:'
  groupsPrefix: 'oidc:'

Before deploying kcp, you need to replace the following placeholder variables in the kcp.values.yaml file with your own values:

  • <DOMAIN>

After you’ve replaced all the placeholders, deploy the kcp Helm chart:

helm upgrade --install kcp kcp \
    --repo=https://kcp-dev.github.io/helm-charts \
    --version=0.14.0 \
    --create-namespace \
    --namespace=kdp-system \
    --values=kcp.values.yaml

Deploy KDP

Finally, you’ll deploy the main KDP application. It connects to the kcp control plane and includes a one-time bootstrap job that grants the admin user full administrative rights, allowing them to manage the entire platform.

Save the following content to a file named kdp.values.yaml:

# kdp.values.yaml
kdp:
  imagePullSecret: |-
    {
      "auths": {
        "quay.io": {
          "auth": "<PULL_CREDENTIALS>",
          "email": ""
        }
      }
    }

  frontProxy:
    internalDomain: internal.<DOMAIN>
    publicDomain: api.<DOMAIN>
    url: https://internal.<DOMAIN>:8443

  virtualWorkspaces:
    shardExternalURL: https://internal.<DOMAIN>:8443

  hooks:
    bootstrap:
      enabled: true
      extraManifests:
        rbac.yaml: |
          apiVersion: rbac.authorization.k8s.io/v1
          kind: ClusterRoleBinding
          metadata:
            name: admin:cluster-admin
          roleRef:
            apiGroup: rbac.authorization.k8s.io
            kind: ClusterRole
            name: cluster-admin
          subjects:
            - kind: User
              name: oidc:admin

Before deploying KDP, you need to replace the following placeholder variables in the kdp.values.yaml file with your own values:

  • <PULL_CREDENTIALS>
  • <DOMAIN>

With all placeholders replaced, deploy the KDP Helm chart. Use your email address as the username and the license key you received as the password to log into the Helm registry.

helm registry login quay.io
helm upgrade --install kdp \
    oci://quay.io/kubermatic/helm-charts/developer-platform \
    --version=0.9.0 \
    --create-namespace \
    --namespace=kdp-system \
    --values=kdp.values.yaml

Deploy KDP dashboard

Last but not least, you’ll deploy the KDP’s web-based dashboard, which serves as the primary user interface. It’s configured to use Dex for user login and connects to kcp, providing developers with a graphical interface to create and manage their service objects.

Save the following content to a file named kdp-dashboard.values.yaml:

# kdp-dashboard.values.yaml
dashboard:
  imagePullSecret: |-
    {
      "auths": {
        "quay.io": {
          "auth": "<PULL_CREDENTIALS>",
          "email": ""
        }
      }
    }

  config:
    app:
      baseURL: https://dashboard.<DOMAIN>
    authentication:
      encryptionKey: <SESSION_ENCRYPTION_KEY>
      oidc:
        clientID: kdp-kubelogin
        clientSecret: <OIDC_CLIENT_SECRET>
        issuerURL: https://login.<DOMAIN>
    backend:
      frontProxyURL: https://api.<DOMAIN>
    features:
      aiAgent:
        enabled: true
        generatorURL: https://dashboard.<DOMAIN>/ai-agent/
      kubeconfigDownload:
        enabled: true
        serverCA: /app/_config/user-kubeconfig/ca.crt
        serverURL: https://internal.<DOMAIN>:8443

  ingress:
    create: true
    host: dashboard.<DOMAIN>
    certIssuer:
      kind: ClusterIssuer
      name: letsencrypt-prod

  extraVolumeMounts:
    - name: user-kubeconfig-ca
      mountPath: /app/_config/user-kubeconfig
      secretName: kcp-ca
      items:
        - key: tls.crt
          path: ca.crt


Before deploying the KDP dashboard, you need to replace the following placeholder variables in the kdp-dashboard.values.yaml file with your own values:

  • <PULL_CREDENTIALS>
  • <DOMAIN>
  • <OIDC_CLIENT_SECRET>
  • <SESSION_ENCRYPTION_KEY>

The <OIDC_CLIENT_SECRET> placeholder must be replaced with the value generated in step “Deploy Dex” and configured in the dex.values.yaml file.

The <SESSION_ENCRYPTION_KEY> placeholder must - similar to the OIDC client secret - be replaced with a long, random string that the KDP dashboard uses to protect user sessions. You can use the same command, to generate a secure, random string:

cat /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c32

Copy and paste the output as the value for <SESSION_ENCRYPTION_KEY>.

Now that all placeholders are replaced, deploy the KDP dashboard Helm chart. To log into the Helm registry, again use your email address as the username and the license key you received as the password.

helm registry login quay.io
helm upgrade --install kdp-dashboard \
    oci://quay.io/kubermatic/helm-charts/developer-platform-dashboard \
    --version=0.9.0 \
    --create-namespace \
    --namespace=kdp-system \
    --values=kdp-dashboard.values.yaml

Deploy KDP AI Agent

The KDP AI Agent is a backend service that powers AI-driven features in the KDP dashboard. It uses OpenAI to provide two capabilities: spec generation, which converts natural language prompts into properly structured Kubernetes resource YAML, and UI schema generation, which produces custom RJSF UI schemas that tailor the dashboard’s resource creation forms based on a prompt.

Before proceeding, ensure you have an OpenAI API key.

Save the following content to a file named ai-agent.values.yaml:

# ai-agent.values.yaml
aiAgent:
  imagePullSecret: |-
    {
      "auths": {
        "quay.io": {
          "auth": "<PULL_CREDENTIALS>",
          "email": ""
        }
      }
    }

  config:
    oidc:
      clientID: kdp-kubelogin
      clientSecret: <OIDC_CLIENT_SECRET>
      issuerURL: https://login.<DOMAIN>
    kubernetes_api_url: https://api.<DOMAIN>
    openai_api_key: "<OPENAI_API_KEY>"

  ingress:
    create: true
    host: dashboard.<DOMAIN>
    prefix: /ai-agent(/|$)(.*)
    certIssuer:
      kind: ClusterIssuer
      name: letsencrypt-prod


Replace the following placeholder variables:

  • <PULL_CREDENTIALS>
  • <DOMAIN>
  • <OIDC_CLIENT_SECRET> (same value as in the Dex and Dashboard configuration)
  • <OPENAI_API_KEY>

Deploy the AI Agent Helm chart:

helm registry login quay.io
helm upgrade --install kdp-ai-agent \
    oci://quay.io/kubermatic/helm-charts/developer-platform-ai-agent \
    --version=0.9.0 \
    --create-namespace \
    --namespace=kdp-system \
    --values=ai-agent.values.yaml

The AI Agent is served under the same domain as the dashboard (dashboard.<DOMAIN>/ai-agent/) to avoid CORS issues. The NGINX Ingress uses a regex path and rewrite rule to forward requests to the AI Agent service.

Gateway API alternative: If you use a Gateway API controller, disable the AI Agent’s built-in Ingress and create an HTTPRoute with a URL rewrite instead:

# ai-agent.values.yaml
aiAgent:
  ingress:
    create: false

Then add the following HTTPRoute alongside your existing Dex and Dashboard routes. Save the following content to a file named ai-agent.http-route.yaml:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: kdp-ai-agent
  namespace: kdp-system
spec:
  hostnames:
    - dashboard.&lt;DOMAIN&gt;
  parentRefs:
    - name: shared-gateway
      namespace: &lt;GATEWAY_NAMESPACE&gt;
  rules:
    - backendRefs:
        - name: kdp-ai-agent
          port: 8000
      matches:
        - path:
            type: PathPrefix
            value: /ai-agent
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /

Replace <GATEWAY_NAMESPACE> and <DOMAIN>, then apply:

kubectl apply -f ./ai-agent.http-route.yaml

This rewrites /ai-agent/... to /... before forwarding to the AI Agent service, matching the behavior of the NGINX rewrite rule.

For more details, see the AI Agent documentation.

Configure DNS records

In order to finalize the installation and make your KDP instance accessible, you must create four DNS records in your DNS provider. Each record points one of the hostnames you configured to the correct load balancer in your Kubernetes cluster.

The following table summarizes the records:

Hostname Points to Purpose
login.<DOMAIN> Ingress controller / Gateway LB Dex identity provider login page
api.<DOMAIN> Ingress controller / Gateway LB Public API endpoint (reverse proxy to kcp, used by the dashboard)
dashboard.<DOMAIN> Ingress controller / Gateway LB KDP dashboard web interface
internal.<DOMAIN> kcp LoadBalancer service Direct kcp API access (used by kubectl and the api-syncagent)

The first three records should all point to the same ingress controller or Gateway load balancer. If you use the NGINX ingress controller, retrieve its external IP or DNS name (assuming it’s installed into the ingress-nginx namespace):

kubectl --namespace=ingress-nginx get service ingress-nginx-controller
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP                                                    PORT(S)                      AGE
ingress-nginx-controller   LoadBalancer   10.47.248.232   4cdd93dfab834ed9a78858c7f2633380.eu-west-1.elb.amazonaws.com   80:30807/TCP,443:30184/TCP   449d

If you use a Gateway API controller instead, point login.<DOMAIN> and dashboard.<DOMAIN> to the external IP of your Gateway’s load balancer service (e.g. kubectl get service -n <gateway-namespace> <envoy-service>). Note that api.<DOMAIN> is created as an NGINX Ingress by the KDP chart. If you are fully replacing NGINX Ingress with a Gateway API controller, you will need to disable the KDP chart’s built-in Ingress (set kdp.frontProxy.publicDomain to empty) and create an additional Gateway listener and HTTPRoute for api.<DOMAIN> pointing to kcp-front-proxy:8443.

The fourth record, internal.<DOMAIN>, points to kcp’s dedicated LoadBalancer service (not the ingress controller). Use the following command to retrieve the external IP address or DNS name of kcp’s load balancer:

kubectl --namespace=kdp-system get service kcp-front-proxy
NAME              TYPE           CLUSTER-IP      EXTERNAL-IP                                                    PORT(S)             AGE
kcp-front-proxy   LoadBalancer   10.240.20.65    99f1093e45d6482d95a0c22c4a2bd056.eu-west-1.elb.amazonaws.com   8443:30295/TCP      381d

Verify the installation

Before accessing the dashboard, verify that all components are running:

kubectl --namespace=kdp-system get pods

You should see pods for dex, kcp, kdp-controller-manager, kdp-virtual-workspaces, kdp-dashboard, and kdp-ai-agent in a Running state. The kdp-bootstrap job should show as Completed. If any pod is stuck in CrashLoopBackOff or Pending, inspect its logs with kubectl --namespace=kdp-system logs <pod-name> for troubleshooting.

Access the dashboard

Congratulations, your KDP installation is now complete! Once your DNS records have propagated, you can access the dashboard by navigating your web browser to the URL you configured (https://dashboard.<DOMAIN>).

You will be redirected to the Dex login page and you can use the administrative credentials that were created during the setup:

  • Username: admin
  • Password: The password you chose in step Deploy Dex

After logging in, you will be taken to the KDP dashboard, where you can begin exploring your platform. Welcome to KDP!

Next steps

Now that your platform is running, here are a few things to try:

  • Use kubectl with kcp: Download a kubeconfig from the dashboard (available in the workspace context menu) or set one up manually using the kcp kubectl plugin. See kcp on the Command Line for a walkthrough.
  • Create your first service: Follow the Your First Service tutorial to register a KDP Service and make custom APIs available to your users.
  • Add users and configure RBAC: Invite team members via the dashboard and set up roles. See RBAC in KDP for details on the role propagation model.
  • Set up the api-syncagent: If you have external Kubernetes clusters with CRDs you want to expose in KDP, install the api-syncagent and define PublishedResource objects to start syncing.
  • Monitor KDP: The KDP exporter exposes Prometheus metrics on port 8385 and supports ServiceMonitor for Prometheus Operator. Enable it via kdp.exporter.serviceMonitor.enabled: true in the KDP Helm values.