CookbookDeployment
Run Olympus on Kubernetes via Helm
Reference Helm chart for K8s deployments
Olympus is designed for Podman + Compose. If you really need Kubernetes (you probably don't), here's a working pattern. Note: this is community-supported, not the project's preferred path.
Chart layout
olympus-helm/
├── Chart.yaml
├── values.yaml
└── templates/
├── postgres.yaml
├── kratos.yaml
├── hydra.yaml
├── hera.yaml
├── athena.yaml
├── caddy.yaml
├── secrets.yaml
└── ingress.yamlvalues.yaml
domain: your-domain.com
postgres:
image: postgres:16
size: 20Gi
storageClass: standard
kratos:
image: oryd/kratos:v1.2.0
publicUrl: https://ciam.your-domain.com/kratos
adminUrl: http://kratos-admin:5001
hydra:
image: oryd/hydra:v2.2.0
publicUrl: https://ciam.your-domain.com
adminUrl: http://hydra-admin:5005
hera:
image: ghcr.io/olympusoss/hera:v1.4.0
replicas: 2
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
athena:
image: ghcr.io/olympusoss/athena:v1.4.0
replicas: 2Hera deployment
# templates/hera.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hera
spec:
replicas: {{ .Values.hera.replicas }}
selector: { matchLabels: { app: hera } }
template:
metadata: { labels: { app: hera } }
spec:
containers:
- name: hera
image: {{ .Values.hera.image }}
envFrom:
- secretRef: { name: hera-secrets }
resources:
{{- toYaml .Values.hera.resources | nindent 12 }}
ports:
- containerPort: 3000
readinessProbe:
httpGet: { path: /healthz, port: 3000 }
livenessProbe:
httpGet: { path: /healthz, port: 3000 }
initialDelaySeconds: 30
---
apiVersion: v1
kind: Service
metadata: { name: hera }
spec:
selector: { app: hera }
ports:
- port: 3000Postgres as StatefulSet
# templates/postgres.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: postgres }
spec:
serviceName: postgres
selector: { matchLabels: { app: postgres } }
template:
metadata: { labels: { app: postgres } }
spec:
containers:
- name: postgres
image: {{ .Values.postgres.image }}
envFrom: [{ secretRef: { name: postgres-secrets } }]
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata: { name: data }
spec:
accessModes: [ReadWriteOnce]
storageClassName: {{ .Values.postgres.storageClass }}
resources: { requests: { storage: {{ .Values.postgres.size }} } }For prod, use a managed Postgres (RDS, Cloud SQL) instead of a self-hosted StatefulSet. The Olympus project does not test against self-hosted K8s Postgres.
Ingress
# templates/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: olympus
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: [ciam.{{ .Values.domain }}, iam.{{ .Values.domain }}]
secretName: olympus-tls
rules:
- host: ciam.{{ .Values.domain }}
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: hera, port: { number: 3000 } } }
- path: /oauth2
pathType: Prefix
backend: { service: { name: hydra-public, port: { number: 4444 } } }
- path: /kratos
pathType: Prefix
backend: { service: { name: kratos-public, port: { number: 5000 } } }
- host: iam.{{ .Values.domain }}
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: athena, port: { number: 3000 } } }Install
helm install olympus . --values values.yaml --namespace olympus --create-namespaceCaveats
- Caddy's rate-limit module: doesn't have a Helm chart. You'd need to build a custom Caddy image or fall back to Nginx + an external WAF.
- Encryption-key startup hardening: relies on container init scripts. Port to K8s init containers.
- CI: Olympus's pipelines aren't tested against K8s. Drift between Podman behavior and K8s pod behavior can surface bugs unique to your env.
- Operational cost: K8s adds significant baseline cost. For < 100k MAU, it's overkill.
When K8s is right
- You already operate a K8s cluster for other workloads.
- You need horizontal scaling beyond a single VPS.
- You have K8s ops expertise on the team.
If none apply, use Podman + Compose.