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/eddytorThis 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_KEYmust be base64 of exactly 32 bytes;EDDYTOR_API_KEY_SECRETis 32+ random bytes.- For bundled Postgres the DB URL host MUST be
eddytor-postgresand the creds must matchpostgres.user/password/database(defaults alleddytor). 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.comIngress + 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-runhelm upgrade --reuse-values --set config.publicUrl=http://<IP>:8080(two-step - the IP isn't known until provisioned, andpublicUrlmust match the address users hit). - No ingress yet:
kubectl -n eddytor port-forward svc/eddytor-server 8080:8080and 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 + engine5. 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.comSee Bootstrap the first admin.
Gotchas
- Install from the OCI chart only - cloning/building is a maintainer path.
- Use the
:k8simages (chart default).:latestis 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 capsCHOWN,FOWNER,DAC_OVERRIDE,SETGID,SETUID.