Skip to main content
Openfuse

Kubernetes

Deploy Openfuse on Kubernetes with Deployments, Jobs, Secrets, and Ingress. Includes manifests for the API, Keycloak, UI, and the Config Importer as a Helm hook.

This guide covers deploying Openfuse on Kubernetes. For the single-server Docker Compose setup, see Docker Compose.

Prerequisites

  • A Kubernetes cluster (1.26+)
  • A managed PostgreSQL 17+ instance with two databases: openfuse and keycloak
  • An Ingress controller or Gateway API implementation
  • Wildcard DNS records pointing to your ingress

Routing

Configure your Ingress or Gateway to route by hostname:

PatternTargetPort
*.api.<ROOT_DOMAIN>API Service3000
sso.<ROOT_DOMAIN>Keycloak Service8080
Everything elseUI Service80

TLS termination happens at the Ingress. Use cert-manager for automated certificate management.

Secrets

openfuse-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: openfuse-secrets
type: Opaque
stringData:
  DATABASE_PASSWORD: 'your-db-password'
  SESSION_SECRET: 'your-session-secret-32-chars-min'
  KC_BOOTSTRAP_ADMIN_PASSWORD: 'your-keycloak-admin-password'
  KC_STAFF_BACKEND_CLIENT_SECRET: 'generated-secret'
  KC_STAFF_BFF_CLIENT_SECRET: 'generated-secret'
  KC_TENANTS_BACKEND_CLIENT_SECRET: 'generated-secret'
  KC_TENANTS_BFF_CLIENT_SECRET: 'generated-secret'
  KC_TENANTS_SDK_CLIENT_SECRET: 'generated-secret'

For production, use External Secrets Operator or your cloud provider's secret store CSI driver instead of static Secret manifests.

Config Importer

Run the Config Importer as a Job. If you use Helm, add it as a pre-install/pre-upgrade hook:

config-importer-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: openfuse-config-importer
  annotations:
    'helm.sh/hook': pre-install,pre-upgrade
    'helm.sh/hook-delete-policy': before-hook-creation
spec:
  ttlSecondsAfterFinished: 300
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: config-importer
          image: ghcr.io/openfuseio/openfuse-config-importer:1.0.0
          env:
            - name: KEYCLOAK_URL
              value: 'http://keycloak:8080'
            - name: KEYCLOAK_USER
              value: 'admin'
            - name: KEYCLOAK_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: openfuse-secrets
                  key: KC_BOOTSTRAP_ADMIN_PASSWORD
            - name: KEYCLOAK_AVAILABILITYCHECK_ENABLED
              value: 'true'
            - name: IMPORT_FILES_LOCATIONS
              value: '/config/*'
            - name: IMPORT_VARSUBSTITUTION_ENABLED
              value: 'true'
            - name: IMPORT_VARSUBSTITUTION_UNDEFINEDISTERROR
              value: 'true'
            # ... remaining env vars

The image has realm YAML files baked in — no volume mounts needed. See the configuration reference for all Config Importer variables.

Run on first deployment and on upgrades that include realm config changes. Skip on routine restarts or scaling events.

API Deployment

api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openfuse-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: openfuse-api
  template:
    metadata:
      labels:
        app: openfuse-api
    spec:
      containers:
        - name: api
          image: ghcr.io/openfuseio/openfuse-api:1.0.0
          ports:
            - containerPort: 3000
          envFrom:
            - secretRef:
                name: openfuse-secrets
          env:
            - name: ROOT_DOMAIN
              value: 'openfuse.example.com'
            - name: DATABASE_HOST
              value: 'your-rds-endpoint.amazonaws.com'
            - name: DATABASE_SSL
              value: 'true'
            - name: KEYCLOAK_URL
              value: 'http://keycloak:8080'
            - name: KEYCLOAK_EXTERNAL_URL
              value: 'https://sso.openfuse.example.com'
            # ... remaining env vars
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 30
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10

The API is stateless — sessions are encrypted into cookies using SESSION_SECRET, so no sticky sessions or session affinity is needed. Scale freely with multiple replicas. All replicas must share the same SESSION_SECRET. Database migrations use an advisory lock, so concurrent startups won't conflict.

Keycloak

Deploy Keycloak as a Deployment (single replica) or StatefulSet (if clustering):

keycloak-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
spec:
  replicas: 1
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
        - name: keycloak
          image: ghcr.io/openfuseio/openfuse-keycloak:1.0.0
          args: ['start']
          ports:
            - containerPort: 8080
            - containerPort: 9000
          env:
            - name: KC_DB
              value: 'postgres'
            - name: KC_DB_URL
              value: 'jdbc:postgresql://your-host:5432/keycloak?sslmode=require'
            - name: KC_HOSTNAME
              value: 'sso.openfuse.example.com'
            - name: KC_PROXY_HEADERS
              value: 'xforwarded'
            - name: KC_HTTP_ENABLED
              value: 'true'
            - name: KC_HEALTH_ENABLED
              value: 'true'
            # ... remaining env vars
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 9000
            initialDelaySeconds: 60

A single instance handles most workloads. For HA, see the Keycloak Clustering Guide.

UI

The UI is a static SPA. Deploy as a Deployment or serve from a CDN:

ui-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openfuse-ui
spec:
  replicas: 2
  selector:
    matchLabels:
      app: openfuse-ui
  template:
    metadata:
      labels:
        app: openfuse-ui
    spec:
      containers:
        - name: ui
          image: ghcr.io/openfuseio/openfuse-ui:1.0.0
          ports:
            - containerPort: 80
          env:
            - name: VITE_API_BASE_HOST
              value: 'api.openfuse.example.com'
            - name: VITE_UI_BASE_HOST
              value: 'openfuse.example.com'

Alternatively, extract the static files and serve from any static host (S3 + CloudFront, Vercel, Netlify). Provide a config.js at the root for runtime configuration:

config.js
window.__OPENFUSE_CONFIG__ = {
  VITE_API_BASE_HOST: 'api.openfuse.example.com',
  VITE_API_PROTOCOL: 'https',
  VITE_APP_ENV: 'production',
  VITE_UI_BASE_HOST: 'openfuse.example.com',
  VITE_UI_PROTOCOL: 'https',
}

Configure SPA fallback (serve index.html for all routes). This eliminates one container and lets you use CDN caching.

Next steps

On this page