Olympus Docs
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.yaml

values.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: 2

Hera 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: 3000

Postgres 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-namespace

Caveats

  • 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.

On this page