| name | eddytor-deploy-gke |
| description | Deploys self-hosted Eddytor on Google Kubernetes Engine. Activates for GKE, Google Kubernetes Engine, gcloud, deploy on GCP, GKE Autopilot, Cloud SQL for PostgreSQL, GCS bucket storage, Workload Identity, or a Google-managed ingress certificate — even without the phrase "deploy on GKE." |
| license | CC-BY-NC-4.0 |
| metadata | {"author":"eddytor","version":"1.0"} |
Deploy Eddytor on GKE
GKE-specific deltas on top of eddytor-deploy-helm (the cluster-agnostic base).
That base owns the chart install, the three required secrets, the verify steps,
and first-admin provisioning — follow it for all of those and only apply the GKE
specifics below. Install from the OCI chart oci://ghcr.io/nordalf/charts/eddytor;
never clone or build.
Gather inputs first (prompt the user — never assume)
Before creating anything, ask the operator (use AskUserQuestion) and use their
answers in every command. The names below are examples, not defaults. Confirm:
- Project (
gcloud config get-value project) and region/zone.
- Autopilot vs Standard (Autopilot manages nodes; Standard needs a node pool).
- Cluster name (example
eddytor).
- Node size + count (Standard only).
- Networking — which VPC, if any:
- Use an existing VPC: pass
--network <vpc> and --subnetwork <subnet> (VPC-native).
Pick this to reach Cloud SQL over private IP or to fit an existing topology.
- Default network: omit those flags.
- Private nodes? add
--enable-private-nodes (and plan master-authorized networks).
- Kubernetes namespace (example
eddytor).
Only proceed once these are settled — the operator owns what gets created.
Default procedure
- Create/get a cluster, then fetch credentials (below).
- Stand up Cloud SQL for PostgreSQL and build the
EDDYTOR_DATABASE_URL
(with ?sslmode=require) into the secret from the base skill.
helm upgrade --install with --set waitForDb.enabled=true for external DB,
plus your ingress/LoadBalancer choice.
- Verify + provision first admin per the base skill.
- Register a GCS bucket after install — see eddytor-storage-registration.
Prefer Workload Identity over a static key.
Tool usage examples
Cluster (Autopilot recommended)
gcloud container clusters create-auto "$CLUSTER" --region="$REGION"
gcloud container clusters create "$CLUSTER" --region="$REGION" \
--num-nodes="$NODES" --machine-type="$MACHINE" \
--workload-pool="$PROJECT_ID.svc.id.goog"
gcloud container clusters get-credentials "$CLUSTER" --region="$REGION"
kubectl create namespace eddytor
Cloud SQL for PostgreSQL
Connect via private IP (simplest with a VPC-native cluster) or the Cloud SQL
Auth Proxy sidecar. Put the resulting connection string in the secret with TLS
required:
--from-literal=EDDYTOR_DATABASE_URL="postgres://eddytor:PW@10.x.x.x:5432/eddytor?sslmode=require"
Install with the external-DB wait gate enabled:
helm upgrade --install eddytor oci://ghcr.io/nordalf/charts/eddytor -n eddytor \
--set secrets.existingSecret=eddytor-secrets \
--set waitForDb.enabled=true \
--set config.publicUrl="https://eddytor.example.com" \
--set ingress.enabled=true --set ingress.host=eddytor.example.com
GCS storage via Workload Identity (preferred)
Bind the Eddytor Kubernetes ServiceAccount to a GCP service account that holds
roles/storage.objectAdmin on the bucket — no static key in the cluster:
gcloud iam service-accounts create eddytor-gcs
gsutil iam ch \
serviceAccount:eddytor-gcs@PROJECT_ID.iam.gserviceaccount.com:roles/storage.objectAdmin \
gs://your-eddytor-bucket
gcloud iam service-accounts add-iam-policy-binding \
eddytor-gcs@PROJECT_ID.iam.gserviceaccount.com \
--role=roles/iam.workloadIdentityUser \
--member="serviceAccount:PROJECT_ID.svc.id.goog[eddytor/eddytor-engine]"
kubectl -n eddytor annotate serviceaccount eddytor-engine \
iam.gke.io/gcp-service-account=eddytor-gcs@PROJECT_ID.iam.gserviceaccount.com
Then register the bucket with register_gcs_storage (params in
eddytor-storage-registration). With Workload Identity the pod gets credentials
ambiently — no key file needed.
Ingress / TLS
--set ingress.enabled=true --set ingress.host=eddytor.example.com \
--set ingress.className=gce --set ingress.tls=true
Raw external IP, no DNS — use a LoadBalancer, then do the two-step publicUrl
update from the base skill (the LB IP is unknown until provisioned):
--set server.service.type=LoadBalancer
helm upgrade --reuse-values eddytor oci://ghcr.io/nordalf/charts/eddytor -n eddytor \
--set config.publicUrl=http://<EXTERNAL-IP>:8080
Teardown
gcloud container clusters delete eddytor --region=europe-west1
Gotchas
- Bundled Postgres crash-loops on the GCE persistent-disk CSI with
chown: Operation not permitted — the chart drops all Linux caps and the PD
volume needs them. Either patch the StatefulSet to add caps
CHOWN,FOWNER,DAC_OVERRIDE,SETGID,SETUID, or (recommended) use Cloud SQL
and skip postgres.bundled. Local hostPath clusters don't hit this.
- Cloud SQL public IPs reject plaintext — keep
?sslmode=require in the DB URL,
or the server's preflight DB handshake fails and it stays in Init.
- Google-managed certs only issue once the Ingress has a stable IP and DNS
resolves to it; the cert sits
Provisioning until both are true.
- The engine Services are fixed names (
eddytor-engine / eddytor-engine-headless)
→ one release per namespace; the Workload Identity binding above targets
the eddytor-engine KSA specifically.
- Autopilot rejects pods that don't fit its resource model — if a pod is stuck
Pending, check it requests CPU/memory within Autopilot limits.
Guidelines
- Cloud SQL for production, bundled only for throwaway eval. For eval that
must stay on bundled Postgres, apply the caps patch above.
- Prefer Workload Identity to mounted GCS keys — fewer secrets to rotate.
- Optional web UI runs on its own origin:
--set ui.enabled=true --set ui.origin=<url>
(expose with its own ui.service.type LoadBalancer/ingress).