Skip to main content

Kubernetes

Deploy EUDIPLO to a cluster with the Kustomize overlays in deployment/k8s. The overlays are a starting point: each workload runs one replica, and the bundled PostgreSQL, RustFS and Vault are single-instance development services. For production, keep the base manifests and point EUDIPLO at a managed database, object store and secret store.

Before you start​

  • kubectl with access to the cluster, and a storage class for persistent volumes
  • An ingress controller of your choice. The bundled Ingress (base/ingress.yaml) uses the class nginx; set spec.ingressClassName to your controller's class (kubectl get ingressclass). The Kubernetes project retired ingress-nginx in March 2026. With the Gateway API, route your HTTPRoutes to the services eudiplo (port 3000) and eudiplo-client (port 80), and remove the Ingress in your overlay with a $patch: delete patch.
  • A choice of overlay: minimal, standard or full (see presets and profiles)

The minimal overlay mounts /app/config as an emptyDir, so the SQLite database is lost when the pod restarts. Use it for short tests only.

1. Create the namespace and secret​

cd deployment/k8s
cp overlays/standard/.env.example overlays/standard/.env
# Replace MASTER_SECRET, AUTH_CLIENT_SECRET, DB_PASSWORD and the RustFS/S3 keys

kubectl create namespace eudiplo
kubectl -n eudiplo create secret generic eudiplo-env \
--from-env-file=overlays/standard/.env

The backend reads every variable from the eudiplo-env secret. DB_HOST, DB_PORT and S3_ENDPOINT are set by the postgres and rustfs components. To change a value later, recreate the secret with --dry-run=client -o yaml | kubectl apply -f - and restart the deployment.

2. Pin the image version​

The base manifests pin 9.0.0; releases don't update that tag. Set the version you want to run in your overlay's kustomization.yaml, and keep backend and web client on the same version:

overlays/standard/kustomization.yaml
images:
- name: ghcr.io/eudiplo/eudiplo
newTag: "9.0.0"
- name: ghcr.io/eudiplo/eudiplo-client
newTag: "9.0.0"

Release images are tagged X.Y.Z, X.Y, X and latest; main and sha-<commit> are builds of the main branch. For reproducible deployments use X.Y.Z or an image digest (digest: sha256:… instead of newTag). The web client shows a warning banner when it and the backend come from different builds.

To upgrade later, back up the database, change newTag for both images, and apply the overlay again. The new backend runs the database migrations on start; with more than one replica, let one replica finish the migration before scaling up (Database). Read the upgrade guide for every major version you cross.

3. Apply the overlay​

kubectl apply -k overlays/standard
kubectl -n eudiplo get pods -w

The full overlay additionally needs VAULT_TOKEN in the secret; see below.

Checkpoint: all pods are Running, the rustfs-bucket-bootstrap job is Completed, and the health endpoint answers:

curl http://eudiplo.localtest.me/health
{
"status": "ok",
"info": { "database": { "status": "up" } },
"error": {},
"details": { "database": { "status": "up" } }
}

/health checks only the database connection. The running version is returned by the authenticated GET /api/version.

Encryption key in the bundled Vault (full)​

The vault-bootstrap job of the full overlay writes a random encryption key to the bundled Vault if none exists. The backend runs with ENCRYPTION_KEY_SOURCE=vault; its init container wait-for-vault holds it back until the key exists.

The bundled Vault runs in development mode and keeps everything in memory. When the vault pod restarts, the key is gone: running backend pods keep working, new ones wait in wait-for-vault. Save the key after the first start (use the VAULT_TOKEN of your secret instead of root):

kubectl -n eudiplo exec deploy/vault -- env VAULT_ADDR=http://127.0.0.1:8200 \
VAULT_TOKEN=root vault kv get -field=key secret/eudiplo/encryption-key

After a Vault restart, write the saved key back the same way with vault kv put secret/eudiplo/encryption-key key=<saved key>. Don't delete and re-run the bootstrap job instead: it would create a new key, and the data encrypted with the old one could no longer be decrypted. Use full only to evaluate Vault. In production, use a Vault with persistent storage or another key source, and back up the key.

Access the services​

The ingress routes eudiplo.localtest.me to the backend and eudiplo-client.localtest.me to the web client; localtest.me resolves to 127.0.0.1. Change the hosts in base/ingress.yaml (or patch them in your overlay) and set PUBLIC_URL to the public backend URL. TLS termination is covered in TLS and reverse proxy.

Without an ingress, forward the ports:

kubectl -n eudiplo port-forward svc/eudiplo 3000:3000
kubectl -n eudiplo port-forward svc/eudiplo-client 4200:80

Use managed services​

Create your own overlay that includes only ../../base and set the connection variables in the secret instead of adding the postgres, rustfs or vault components:

overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: eudiplo
resources:
- ../../base
images:
- name: ghcr.io/eudiplo/eudiplo
newTag: "9.0.0"
- name: ghcr.io/eudiplo/eudiplo-client
newTag: "9.0.0"

The variables are described in Database, Object storage, Encryption keys and KMS. The base deployment mounts /app/config as an emptyDir; mount a ConfigMap or volume there if you use a global kms.json or tenant config folders.

Manage the deployment with the CLI​

Register the cluster as a kubernetes instance to run eudiplo doctor, ps, logs and restart against it. The CLI never applies manifests; see CLI: Kubernetes instances for registration and the required RBAC permissions.

Troubleshooting​

SymptomCauseFix
Pod in CrashLoopBackOff right after startMissing MASTER_SECRET, AUTH_CLIENT_ID or AUTH_CLIENT_SECRET, or an invalid variablekubectl -n eudiplo logs deployment/eudiplo shows the validation error
Ingress gets no address, or requests answer 404No ingress controller serves the class nginxSet spec.ingressClassName to a class from kubectl get ingressclass
Backend cannot reach PostgreSQLDatabase not ready or wrong credentials in the secretkubectl -n eudiplo exec statefulset/postgres -- pg_isready, then fix the secret and restart
ErrImagePull or ImagePullBackOffThe image tag does not exist; release tags have no v prefixkubectl -n eudiplo describe pod <pod> shows the image; correct newTag in images: (step 2)
Pods run an older version than expectedThe overlay sets no images:, so the base tag 9.0.0 is usedSet images: in the overlay (step 2)
kubectl apply fails with field is immutable for a JobThe job's template changed in newer manifestsDelete the completed job (kubectl -n eudiplo delete job <name>) and apply again. vault-bootstrap keeps an existing key; after a Vault restart, restore the key first
kubectl reports an expired certificate for 127.0.0.1:6443Docker Desktop's local cluster certificate expiredReset Kubernetes in Docker Desktop settings

Migrating existing MinIO storage​

Since 9.0 the bundled object storage is RustFS instead of MinIO, with a new rustfs-data volume; follow the upgrade guide to move existing objects.