Harthelm Docs
Install

Install

Install Harthelm with Helm: minimal values, first sign-in and the health check.

Last updated 2026-10-19

This is the generic install: a bundled database, an Ingress controller and cert-manager. The tracks for on-premises, EKS, AKS and GKE say which pieces of each platform to use; the steps here stay the same.

Before you start

  • The cluster meets the requirements and you are cluster admin.
  • helm, kubectl and openssl on your machine, and hart for the last step.
  • The licence file you received with Harthelm, or a trial licence; see Licence.
  • Replace these names everywhere below:
NameStands for
console.example.comthe console host
example.netthe workspace domain; it must be a different registrable domain from the console's
publicyour IngressClass
letsencrypt-dnsa cert-manager ClusterIssuer that can solve DNS-01 for both names
letsencrypt-httpan issuer for your users' custom domains, for example HTTP-01
https://id.example.com/your OIDC issuer
10.0.0.0/16the addresses of your ingress controller's pods
<your registry>, <version>where you got the chart, and the release, for example 1.0.2

Install the operators

Install cert-manager and the CloudNativePG operator with their own charts. Put the operator into cnpg-system, which networkPolicy.cnpgNamespaceSelector expects.

Point DNS at the ingress

Create records for console.example.com and *.example.net that resolve to your Ingress controller's load balancer. DNS and TLS has the details.

Create the sign-in client

At your OIDC provider, create a confidential client with:

  • redirect URI https://console.example.com/auth/oidc/callback;
  • the scopes openid profile email groups;
  • a group, for example harthelm-admins, for the people who administer Harthelm.

Then store its id and secret:

Terminal
kubectl create namespace harthelm-system
kubectl label namespace harthelm-system pod-security.kubernetes.io/enforce=restricted
kubectl -n harthelm-system create secret generic harthelm-oidc \
  --from-literal=clientID=<client id> --from-literal=clientSecret=<client secret>

Provider notes are in Sign-in.

Create the master key

The master key encrypts every stored token and secret: a restored database is useless without it. Core generates one on first start unless you bring your own; create it yourself so it is in your secret store from the start:

Terminal
kubectl -n harthelm-system create secret generic harthelm-master-key \
  --from-literal=active=k1 --from-literal=key.k1="$(openssl rand -base64 32)"

With masterKey.existingSecret set, core refuses to generate another one. Keep a copy outside the cluster.

Store the licence

Every installation needs a licence; without one it starts restricted and refuses new teams and workspaces. Put the file into a Secret:

Terminal
kubectl -n harthelm-system create secret generic harthelm-license --from-file=license=company.license

The values below name it. The preflight checks it with the keys of the release you install. You can also install the licence later in the console or with hart; Licence has the three ways. A licence that names an installation needs its id first: see Order of a fresh install.

Write the values

my-values.yaml
platform:
  host: console.example.com
  workspaceDomain: example.net

# The ingress controller's pods, so limits and audit rows see the real client address.
trustedProxies: [10.0.0.0/16]

routing:
  edge: ingress
  ingress:
    className: public
  tls:
    certManager:
      enabled: true
      issuerRef: {name: letsencrypt-dns, kind: ClusterIssuer, group: cert-manager.io}

oidc:
  issuer: https://id.example.com/
  displayName: Example ID          # the button says "Sign in with Example ID"
  adminGroup: harthelm-admins      # members are cluster admins of Harthelm
  existingSecret: harthelm-oidc

masterKey:
  existingSecret: harthelm-master-key

license:
  existingSecret: harthelm-license

storage:
  class: ""                        # the default StorageClass
  mode: auto

customDomains:
  cnameTarget: edge.example.net    # resolves to your ingress; users CNAME their domains to it
  issuer: {name: letsencrypt-http, kind: ClusterIssuer, group: cert-manager.io}

backup:
  enabled: true
  workspaces:
    enabled: true

Every key is in the chart values reference. The schema is strict: an unknown key or a missing required value fails the render.

Render, then install

Terminal
helm template harthelm oci://<your registry>/charts/harthelm --version <version> \
  -n harthelm-system -f my-values.yaml > /dev/null
helm install harthelm oci://<your registry>/charts/harthelm --version <version> \
  -n harthelm-system -f my-values.yaml

The preflight Job runs first and fails the install with a list of what is missing: an operator, a Secret, a StorageClass, an issuer, a licence this release accepts. Read it with:

Terminal
kubectl -n harthelm-system logs job/harthelm-preflight

Then the database comes up, core migrates it (it has up to 5 minutes at the first start) and the routers start.

Prove the network policies

Terminal
helm test harthelm -n harthelm-system

A probe checks that the NetworkPolicies are enforced, port ranges included. It fails when they are not. Run it again after every CNI change.

Sign in

Open https://console.example.com and click Sign in with Example ID as a member of harthelm-admins. Every installation starts with the organization Default and its team Default (tenancy.defaultOrg, tenancy.defaultTeam). From here, Team administration shows how to create organizations and teams and invite people.

Check the installation

Sign in with hart as a cluster admin and run the doctor:

Terminal
hart login --api https://console.example.com
hart admin doctor --capabilities

Every check should pass, the last one, license, included; Troubleshooting explains each one. The console shows what the cluster offers under Platform → Capabilities and, since 1.5.0, runs the checks themselves under Platform → Doctor (The Doctor page).

From Harthelm 1.2 on, hart smoke then walks the path a user takes, end to end: it creates a scratch workspace in a team, writes a small app into it, sets an env var, runs Sync, prepare and Run, waits until the app answers on the workspace's dev URL, and deletes the workspace again (purged with a team admin's key):

Terminal
hart smoke --team 1

It passes in well under a minute, needs nothing from the internet, and a member with the Developer role may run it too. A failure names the step that failed; --keep leaves the workspace for a look and -o json prints every step. Then create a first workspace as a test user with the Quickstart.

Order of a fresh install

A licence bound to an installation names the installation's id, and core creates that id at its first start. A fresh installation therefore runs restricted from its first minute (there is no grace period for it), and a restricted installation refuses the first sign-in of a person it has not seen. The order that works:

  1. Install without the licence Secret. Leave license.existingSecret out of the values: with it set, the preflight refuses a missing Secret and stops the install. The database and core start and the console answers; the doctor's license check fails with the sentence that says why and names the installation id. That is expected.

  2. Read the installation id. Since 1.5, core logs it at INFO on every start while no licence is in force:

    Terminal
    kubectl -n harthelm-system logs deploy/harthelm-core -c core | grep "no licence installed"
    # no licence installed; installation id 0b9e4c6a-3f1d-4a8e-9c2b-7d5e1f0a6b3c; see docs/license.md

    The chart's notes print the same command while license.existingSecret is not set: after helm install and helm upgrade, and again with helm status harthelm (Argo CD does not show chart notes). hart admin license shows the id as Installation: to a signed-in cluster admin, which on a fresh install is the break-glass administrator (below). Send the id with your order: a saas or internal licence names it. A licence that names no installation needs no id and can go in with the first sync.

  3. Install the licence. Create the Secret harthelm-license (Store the licence) and set license.existingSecret: harthelm-license; that is the second sync (helm upgrade, or the Argo CD sync of the values commit), and core reads the Secret within a minute without a restart. Or sign in with the break-glass account and run hart admin license install -f company.license, or paste the file in the console.

  4. Sign in through OIDC, as in Sign in.

Break-glass is the bootstrap path. The licence never refuses the break-glass administrator. With auth.breakGlass enabled at the first sync, a restricted installation can be signed in to at once: no id is needed to get in, and the licence can be installed through hart or the console without a second sync. Keep the account afterwards as the way in when the OIDC provider is down. Without it nobody can sign in before the licence is installed, not even an admin who has never signed in before. See Licence.

Optional values

TopicValuesPage
Workspaces from Gitgit.providersGit providers
Invitation mailsmailMail
Workspace and service sizesplansWorkspaces
The default team quotateams.quotaTeam administration
Where pods runschedulingWorkspaces
Emergency accessauth.breakGlassSign-in

On this page