go-tell/deploy/helm/go-tell/README.md
2026-10-03 04:41:31 +02:00

9 KiB

Go Tell Helm chart

This chart deploys Go Tell as one image: a Go binary that serves the API and embeds the React frontend, with a persistent volume for the content file. It publishes a single HTTPS host, https://tell.dev, behind ingress-nginx with a certificate from cert-manager.

1. What the chart creates

Resource Name Purpose
Deployment go-tell CMS binary, API and web UI in one container
Service go-tell Cluster-internal service, ingress backend
Ingress go-tell One rule and one TLS SAN for tell.dev
Issuer go-tell-selfsigned cert-manager self-signed issuer
Certificate go-tell-ca Chart CA that signs the leaf certificate
Certificate go-tell Leaf certificate for tell.dev
PersistentVolumeClaim go-tell Content file storage, emptyDir when disabled
Secret go-tell-secrets Go Loose client credentials and the proxy token

There are no tenants and no per-tenant hosts: one release, one host, one content file.

2. Prerequisites

  • A Kubernetes cluster reachable through KUBECONFIG, by default ~/.kube/config.
  • A StorageClass when persistence.enabled is true.
  • ingress-nginx with the nginx ingress class: make ingress-nginx-install.
  • cert-manager when ingress.tls.enabled is true: make cert-manager-install.
  • Cluster nodes trusting batty1039.startdedicated.net:5000 as an insecure registry, or an HTTPS registry.

Run everything the chart depends on in one step:

make cluster-prepare

3. Deploy

make helm-lint
make helm-deploy-development

The development profile runs the demo profile on NodePort 30010 with ephemeral content and Go Loose disabled, which is what make frontend talks to. Production runs the persisted content file with browser sign-in:

export GO_LOOSE_CLIENT_ID=...
export GO_LOOSE_CLIENT_SECRET=...
make helm-deploy-production

Credentials are never written into a values file. They reach the release secret through --set, or through the GO_LOOSE_CLIENT_ID and GO_LOOSE_CLIENT_SECRET secrets of the deploy workflow. The chart refuses to render when goLoose.enabled is true and either of them is missing, because a release without credentials would have no way to sign anybody in.

After a successful install the release notes list the URL and the command that exports the CA certificate. With the local platform, prefer docs/KUBERNETES-DEPLOYMENTS.md in the infrastructure repository, which covers /etc/hosts entries and CA trust for the whole platform.

4. The single host

host is the only host the release serves:

host: tell.dev

The chart derives, without any extra configuration:

  • One ingress rule for that host.
  • One certificate SAN for that host, so a single secret covers the release.
  • GO_LOOSE_APP_DOMAIN, which the API turns into the redirect URI https://tell.dev/api/auth/callback.

That redirect URI has to be registered on the Go Loose application together with the client id and secret. goLoose.tenant names the Go Loose tenant that owns the login, which decides where the browser is sent first:

https://tell.dev/api/auth/login
  -> https://tell.auth.dev/connect/authorize
  -> https://tell.dev/api/auth/callback

5. Values

Top level

Key Default Description
nameOverride "" Overrides the chart name in resource names and labels
fullnameOverride "" Replaces the generated resource name prefix
host tell.dev Public host of the CMS
replicaCount 1 CMS replicas, the content file is shared through the volume
imagePullSecrets [] Pull secrets added to the pod
nodeSelector, tolerations, affinity empty Scheduling of the deployment

image and app

Key Default Description
image.repository batty1039.startdedicated.net:5000/go-tell CMS image
image.tag latest CMS image tag
image.pullPolicy IfNotPresent CMS pull policy
app.profile production CMS_PROFILE, demo enables the in-memory seed and the sudo terminal
app.allowedOrigins https://tell.dev CMS_ALLOWED_ORIGINS
app.contentFile /data/content.json CMS_CONTENT_FILE, ignored when persistence is disabled
app.postgresURL "" CMS_POSTGRES_URL, reserved for the PostgreSQL adapter
app.ssoHeader X-SSO-Token CMS_SSO_HEADER, empty disables the proxy identity
app.ssoCookie "" CMS_SSO_COOKIE, cookie carrying the same value
app.resources 100m/128Mi to 500m/512Mi Requests and limits
app.podAnnotations, app.podLabels {} Extra pod metadata

goLoose

Key Default Description
goLoose.enabled true Injects the Go Loose environment into the pod
goLoose.tenant tell Go Loose tenant that owns the login, one DNS label
goLoose.authDomain auth.dev Identity domain shared by every Go Loose tenant
goLoose.appDomain tell.dev Public host, which determines the redirect URI
goLoose.clientID "" Client id of the Go Loose application, required when enabled
goLoose.clientSecret "" Client secret of the Go Loose application, required when enabled
goLoose.ca.existingSecret "" Secret holding the CA bundle of an internally signed identity domain
goLoose.ca.key ca.crt Key projected from that secret
goLoose.ca.mountPath /certs/local-ca.crt GO_LOOSE_CA_FILE target
secrets.ssoToken "" Shared secret the proxy header value must match

The CA bundle is only mounted when goLoose.ca.existingSecret is set. A publicly signed identity domain therefore needs no extra configuration, while an internally signed one, such as the local platform, has to publish its certificate first:

kubectl -n go-tell-production create secret generic local-ca --from-file=ca.crt=certs/local-ca.crt

ingress and certManager

Key Default Description
ingress.enabled true Creates the ingress
ingress.className nginx Ingress class of the controller
ingress.annotations TLS redirect Controller annotations
ingress.path / Path routed to the service
ingress.pathType Prefix Path type of that route
ingress.tls.enabled true Terminates TLS on the controller
ingress.tls.secretName "" Defaults to <fullname>-tls
ingress.tls.existingSecret "" Uses a certificate managed outside the chart
certManager.enabled true Chart creates the certificates
certManager.issuerRef {} Uses an existing issuer instead of the chart CA
certManager.duration 8760h Certificate lifetime
certManager.renewBefore 720h Renewal window
certManager.caSecretName "" Defaults to <fullname>-ca

Four certificate objects are created: a self-signed Issuer, a CA Certificate stored in caSecretName, an Issuer that serves that CA, and the leaf Certificate stored in the ingress TLS secret for host. Set certManager.issuerRef to sign with a cluster issuer instead:

certManager:
  issuerRef:
    name: internal-acme
    kind: ClusterIssuer
    group: cert-manager.io

Set ingress.tls.existingSecret to use a certificate the chart does not own, and certManager.enabled: false to stop emitting cert-manager objects.

Storage and pod security

Key Default Description
persistence.enabled true Persistent volume for the content file
persistence.size 2Gi Volume size
persistence.accessModes [ReadWriteOnce] Volume access modes
persistence.storageClass "" Storage class, the cluster default when empty
podSecurityContext fsGroup: 10001 Pod security context, gives the volume to the image user
securityContext non-root 10001, read-only root filesystem Container security context
strategy.type Recreate The old pod releases the volume before the new one starts

The image runs as its own go-tell user (uid 10001) and declares no HOME, so readOnlyRootFilesystem needs the writable /tmp mount the chart adds.

6. Upgrade and remove

make helm-deploy-production
helm -n go-tell-production history go-tell
helm -n go-tell-production rollback go-tell 1
make helm-down-production

Keep the Go Loose credentials outside the cluster: uninstalling removes the release secret, and Go Loose shows a new secret only once.

7. Verify

kubectl -n go-tell-production get ingress go-tell
kubectl -n go-tell-production get certificate
kubectl -n go-tell-production rollout status deployment/go-tell
curl --resolve tell.dev:443:127.0.0.1 --cacert go-tell-ca.crt https://tell.dev/health
curl -I --cacert go-tell-ca.crt https://tell.dev/api/auth/login

The login request must answer 302 towards https://tell.auth.dev/connect/authorize; a 404 means the release has no Go Loose credentials. The test pod installed by helm test checks the health endpoint from inside the cluster.