EddytorDocs & API
02 · Deploy & RunKubernetes with Helm

Install the chart (OCI artifact)

Install self-hosted Eddytor on any Kubernetes cluster from the published OCI Helm chart.

The chart is published as an OCI artifact - install it directly, never clone or build. It defaults to the :k8s images, where the server discovers engine replicas via DNS. Needs Helm ≥ 3.8 (OCI support).

Chart: oci://ghcr.io/nordalf/charts/eddytor

This is the cluster-agnostic base. Cloud specifics (cluster creation, managed Postgres, keyless storage, ingress) live in AKS, GKE / EKS, and local clusters.

1. Namespace + required secret

Three values are mandatory (the server validates them at boot):

kubectl create namespace eddytor

kubectl -n eddytor create secret generic eddytor-secrets \
  --from-literal=EDDYTOR_DATABASE_URL="postgres://eddytor:eddytor@eddytor-postgres:5432/eddytor" \
  --from-literal=EDDYTOR_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
  --from-literal=EDDYTOR_API_KEY_SECRET="$(openssl rand -base64 32)"
  • EDDYTOR_ENCRYPTION_KEY must be base64 of exactly 32 bytes; EDDYTOR_API_KEY_SECRET is 32+ random bytes.
  • For bundled Postgres the DB URL host MUST be eddytor-postgres and the creds must match postgres.user/password/database (defaults all eddytor). A mismatch is the #1 install failure.

2. Install

helm upgrade --install eddytor oci://ghcr.io/nordalf/charts/eddytor -n eddytor \
  --set secrets.existingSecret=eddytor-secrets \
  --set postgres.bundled=true \
  --set garage.bundled=true \
  --set config.publicUrl="https://eddytor.example.com" \
  --set ingress.enabled=true --set ingress.host=eddytor.example.com

Ingress + autoscaling (HPA) + PodDisruptionBudgets are on by default.

Evaluation vs production

  • Evaluation: postgres.bundled=true + garage.bundled=true. Single replica, no HA, no backups.
  • Production: leave both false, put your external DB URL in the secret, and register a real object store after install. See Bundled vs external.

Heads up

For an external DB, leave waitForDb.enabled=false (the default). Its init container probes the bundled Service name eddytor-postgres regardless of your DB URL, so with external Postgres it hangs pods in Init. The server runs its own preflight + advisory-locked migration anyway.

3. Reach the server

  • Ingress + DNS: --set ingress.enabled=true --set ingress.host=… --set ingress.className=… and --set config.publicUrl=https://that-host.
  • Raw public IP, no DNS: --set server.service.type=LoadBalancer, then read the assigned IP and re-run helm upgrade --reuse-values --set config.publicUrl=http://<IP>:8080 (two-step - the IP isn't known until provisioned, and publicUrl must match the address users hit).
  • No ingress yet: kubectl -n eddytor port-forward svc/eddytor-server 8080:8080 and install with --set config.publicUrl=http://localhost:8080.

4. Verify (proves the DB handshake)

kubectl -n eddytor rollout status statefulset/eddytor-postgres --timeout=180s   # bundled only
kubectl -n eddytor rollout status deploy/eddytor-server --timeout=300s
kubectl -n eddytor logs deploy/eddytor-server | grep -iE "preflight|migrat|listening"
helm test eddytor -n eddytor          # hits /healthz on server + engine

5. First admin

kubectl -n eddytor exec deploy/eddytor-server -- \
  eddytoradm setup --email you@example.com --org "Your Org"
# headless key instead of browser login:
kubectl -n eddytor exec deploy/eddytor-server -- \
  eddytoradm create-api-key --email you@example.com

See Bootstrap the first admin.

Gotchas

  • Install from the OCI chart only - cloning/building is a maintainer path.
  • Use the :k8s images (chart default). :latest is the community/compose edition and won't do DNS engine discovery.
  • One release per namespace - the engine Services are fixed-named (eddytor-engine / eddytor-engine-headless); don't rename them.
  • Bundled Postgres on a real CSI volume (azuredisk/EBS/PD) may crash-loop with chown … Operation not permitted. Use external Postgres, or patch the StatefulSet to add caps CHOWN,FOWNER,DAC_OVERRIDE,SETGID,SETUID.

Next

On this page