Skip to main content

Deploy on GKE

The GKE path is the most locked-down configuration the chart supports: no database password exists anywhere, no storage keys exist anywhere, and Google authenticates every request before it reaches the app.

Start from charts/presponsievelite/values-gke.example.yaml and replace every <PLACEHOLDER>.

What makes this path different

No database password. The Cloud SQL Auth Proxy runs as a native sidecar, authenticates with the pod's Workload Identity, and connects with IAM database auth. The chart injects DATABASE_URL pointing at the proxy's local listener. There is no credential to rotate or leak.

This needs Kubernetes 1.29 or later, because native sidecars are an initContainer with restartPolicy: Always.

No storage keys. STORAGE_BACKEND: gcs uses native GCS through Application Default Credentials, which resolve to the pod's Workload Identity. Supply only the bucket name.

No login screen. IAP authenticates with Google at the load balancer, and the app trusts the forwarded x-goog-authenticated-user-email header.

Prerequisites

gcloud services enable \
container.googleapis.com sqladmin.googleapis.com \
secretmanager.googleapis.com iap.googleapis.com \
--project <PROJECT_ID>

gcloud compute addresses create presponsieve-ip --global

The service account needs roles/cloudsql.client, roles/cloudsql.instanceUser, roles/storage.objectAdmin on the bucket, and roles/iam.serviceAccountTokenCreator on itself.

warning

That last binding is the one people miss. Workload Identity credentials carry no private key, so signing V4 download URLs goes through IAM signBlob. Without it, everything works except report downloads, which fail with a signing error that does not obviously point at IAM.

Enable IAM database authentication

On the instance, set the cloudsql.iam_authentication flag, then create the IAM user. Cloud SQL takes the service account email with .gserviceaccount.com stripped:

gcloud sql users create presponsieve@<PROJECT_ID>.iam \
--instance=presponsieve-pg --type=CLOUD_IAM_SERVICE_ACCOUNT

The same string goes in cloudSqlProxy.iamUser.

Values

serviceAccount:
create: true
annotations:
iam.gke.io/gcp-service-account: presponsieve@<PROJECT_ID>.iam.gserviceaccount.com

cloudSqlProxy:
enabled: true
instanceConnectionName: <PROJECT_ID>:<REGION>:presponsieve-pg
iamUser: presponsieve@<PROJECT_ID>.iam
dbName: presponsieve

postgresql:
enabled: false
externalDatabase:
url: "" # unused; the proxy supplies DATABASE_URL

minio:
enabled: false
externalObjectStore:
endpointUrl: ""
bucket: <PROJECT_ID>-presponsieve-artifacts
region: <REGION>

app:
existingSecret: presponsieve-secrets
config:
STORAGE_BACKEND: "gcs"
GCS_SIGNER_SERVICE_ACCOUNT: presponsieve@<PROJECT_ID>.iam.gserviceaccount.com
KMS_BACKEND: "k8s"

keycloak:
enabled: false
oidc:
issuerUrl: ""

gke:
iap:
enabled: true
oauthClientSecretName: presponsieve-iap-oauth

service:
annotations:
cloud.google.com/neg: '{"ingress": true}'

ingress:
enabled: true
className: "gce"
host: app.example.com
annotations:
kubernetes.io/ingress.global-static-ip-name: presponsieve-ip
networking.gke.io/managed-certificates: presponsieve-cert
tls:
enabled: false
managedCertificate:
enabled: true
domains:
- app.example.com

Note that tls.enabled is false. TLS comes from the Google-managed certificate, not a Kubernetes secret. The certificate name must match what the chart renders, which is <fullname>-certpresponsieve-cert with fullnameOverride: presponsieve.

The IAP OAuth client

Create the client in the console, then store it:

kubectl create secret generic presponsieve-iap-oauth -n presponsieve \
--from-literal=client_id="$IAP_CLIENT_ID" \
--from-literal=client_secret="$IAP_CLIENT_SECRET"

Certificate issuance

The managed certificate cannot be issued until DNS resolves to the static address. Expect 15 minutes, occasionally an hour.

kubectl get managedcertificate -n presponsieve

The backend timeout

The GCLB default backend timeout is 30 seconds, shorter than a long analysis. Past it the load balancer returns an HTML 502 that the app never sees and cannot log. The chart renders a BackendConfig with timeoutSec: 600 whenever IAP or gke.backendConfig is enabled.

Audio transcription

TRANSCRIPTION_BACKEND: "gcp" enables Speech-to-Text with speaker diarization, which is what lets a recording be filtered to one person the way an uploaded transcript can. It reads staged audio from your own bucket using Workload Identity, so no extra credentials are needed, but the API must be enabled and the service account needs roles/speech.client.