Quick Start
A Control Plane with local authentication, in three steps. At the end the web app answers on your machine and you can create the first account.1
Deploy a PostgreSQL
Skip this if you already have a database to point at. This one is a throwaway with a password in plain sight.
The Control Plane runs its migrations and the organization bootstrap before it listens, so the database has to be reachable at startup.
postgres.yaml
postgres.yaml
2
Write the values file
values.yaml
API_URL is the address the web app and every Sidecar reach this on — here it is the port-forward, because that is where you will open it. Change it when you publish the service, and read API_URL before you leave it out.3
Install the chart
kubectl delete namespace hoop.
Helm Install
To install the latest version in a new namespace (example:hoop):
A release named
hoopcontrolplane collapses to the bare name rather than repeating it.
Overriding values
It is possible to add new attributes or overwrite an attribute from a basevalues.yaml file. In the example below a specific image tag is pinned and the log level is raised.
Control Plane Configuration
Everything underconfig becomes an environment variable in a Secret attached with envFrom. A change to any of them rolls the pods on helm upgrade: the Deployment carries a checksum of the rendered Secret.
Database
The Control Plane stores its state in PostgreSQL, using theprivate schema for its own tables. This creates the database and a user with the privileges it needs:
If the password contains special characters, URL-encode it in the connection string. Append
?sslmode=disable if your database does not support TLS.pglite:// is refused: it is single-node and serves one connection at a time.
Authentication
Authentication is local by default. The Control Plane manages users and passwords itself and signs its own tokens, so a database and an address are the whole configuration. Setting anyIDP_* key selects OIDC instead:
IDP_CLIENT_ID and IDP_CLIENT_SECRET become required once IDP_ISSUER is set; the chart refuses to render without them. The provider’s callback is <API_URL>/api/callback — register that URL with your identity provider. See Identity Providers for the per-provider settings.
API URL
API_URL is the address the web app and every Sidecar reach this deployment on. It is required, and it must carry a scheme.
http://127.0.0.1:8009 when the variable is absent, which is why the chart requires it: the fallback boots a pod that looks healthy and is not usable.
TLS
Two keys, and one rule: set both to serve HTTPS, leave both empty to serve plaintext.- Base64 encoded
- Path based
The certificate file may carry the root and intermediate CAs as well. Order matters:
httpGet probe you write against a TLS listener must name scheme: HTTPS itself.
Logging
Chart Configuration
Everything below is a chart attribute rather than part of the Control Plane’s environment: how the pod is built, what it is published as, and where it is scheduled.Image Configuration
By default the chart pullshoophq/hoopcontrolplane:latest, which is the Ubuntu flavour.
The web UI is compiled into the binary, so neither flavour carries webapp files and both serve the app with no configuration. That is also what makes a distroless flavour possible.
Container Images covers what each flavour contains.
Probes
The chart renders no probe.readinessProbe and livenessProbe are both empty by default, and whatever you put under either one is rendered verbatim — any handler the Kubernetes Probe schema accepts, plus timings and thresholds.
GET /api/healthz on 8009, under API_URL’s path if it has one. Ten seconds and not five: the listener opens only after the migrations, the organization bootstrap and the identity provider, so an answer at all means the bootstrap finished.
Add scheme: HTTPS inside httpGet when TLS is configured. A tcpSocket check on 8009 serves either listener from one stanza, at the cost of not calling the endpoint:
Service
One Service, one port.type: LoadBalancer with the annotations your cloud provider expects is the whole configuration for a cluster without the Gateway API:
Exposing it with the Gateway API
The chart renders a Gateway and an HTTPRoute rather than an Ingress. This requires the Gateway API CRDs in your cluster —istioctl install for Istio, or the upstream guides for anything else.
parentRefs and rules have working defaults. The default rule is a PathPrefix on API_URL’s path, backed by this chart’s Service on 8009 — which is the whole Control Plane, since it serves one HTTP port and nothing else:
createGateway: false and name it:
createGateway: false with no parentRefs: a route with no parent attaches to nothing and looks installed. It refuses service.enabled: false with no rules for the same reason from the other end — the default route’s backend is that Service, so the route would resolve to nothing and every request would get a 500 from an install that looked fine.
listeners, addresses, infrastructure and backendTLS pass through to the Gateway untouched, and setting rules replaces the default rule entirely. There is no Ingress template and no GRPCRoute.
backendTLS is the client certificate the Gateway presents to backends, for mTLS, and it lives in the Gateway API experimental channel. The standard channel’s Gateway CRD has no such field and prunes it silently, so it renders into the manifest and then disappears from the live object — check with kubectl get gateway <name> -o yaml before relying on it.Validating the backend’s own certificate is a different thing: a BackendTLSPolicy resource, which this chart does not render.Extra Environment Variables
Anything this chart does not render goes inextraSecret, which becomes a second Secret attached with envFrom:
existingSecret references a Secret you manage yourself. The chart never creates or updates it:
extraSecret, then existingSecret.
Extra Volumes
For what the configuration references by path: a TLS certificate underfile:///..., an identity provider CA bundle.
Mount any Secret holding a private key with
defaultMode: 0400. Kubernetes writes secret files 0644 by default.Service Account
Withcreate: true the chart makes one named after the release. With create: false, name points at an account that already exists — the shape workload identity wants.
Replicas and Deployment Strategy
Recreate for the same reason. At one replica, RollingUpdate’s default surge runs two pods through every upgrade, which is two Slack sockets on a timer. The Control Plane holds no sessions, so the short API gap drops nothing. A deployment already running several replicas has accepted that condition — set RollingUpdate there.
Computing Resources
The chart is API-only: it carries no sessions and runs no proxies.Node Selector
This configuration describes a pod that has a node selector,disktype: ssd. This means that the pod will get scheduled on a node that has a disktype=ssd label.
See this documentation for more information.
Tolerations
See this article explaining how to configure tolerationsNode Affinity
See this article explaining how to configure affinity and anti-affinity rulesSecurity Context
Both are empty by default and pass through untouched.Annotations
Naming
nameOverride changes the base the resource names are built from; fullnameOverride replaces the whole name and drops the release prefix.
Generating Manifests
If you prefer using manifests over Helm, we recommend this approach. It allows you to track any modifications to the chart whenever a new version appears. You can apply a diff to your versioned files to identify what has been altered.Next
Connect a Sidecar
Issue a token, point a Sidecar at the Control Plane, and confirm it picked up its configuration.
Environment Variables
Every variable the service reads.