# 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: ```bash make cluster-prepare ``` ## 3. Deploy ```bash 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: ```bash 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: ```yaml 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: ```bash 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 `-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 `-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: ```yaml 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 ```bash 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 ```bash 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.