Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kubernetes (Helm)

Deploy Vouch on Kubernetes using the Helm chart.

Prerequisites

  • Kubernetes cluster (1.24+)
  • Helm 3
  • A persistent volume provisioner (for SQLite) or external PostgreSQL

Install

# Install from OCI registry
helm install vouch-server oci://ghcr.io/vouch-sh/charts/vouch-server \
  --version 0.1.0 \
  --namespace vouch \
  --create-namespace \
  --values my-values.yaml

Values

Key values to configure:

# values.yaml
image:
  repository: ghcr.io/vouch-sh/vouch
  pullPolicy: IfNotPresent
  tag: ""  # defaults to chart appVersion

serviceAccount:
  create: true
  annotations: {}

podSecurityContext:
  fsGroup: 65532

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  runAsUser: 65532
  seccompProfile:
    type: RuntimeDefault

service:
  type: ClusterIP
  port: 3000

# Environment variables for vouch-server
env:
  VOUCH_LISTEN_ADDR: "0.0.0.0:3000"
  VOUCH_DATABASE_URL: "sqlite:/data/vouch.db?mode=rwc"
  VOUCH_RP_ID: "auth.example.com"
  VOUCH_BASE_URL: "https://auth.example.com"
  RUST_LOG: "info,vouch_server=debug"

# Secret environment variables
# Reference an existing secret containing keys like:
# - VOUCH_JWT_SECRET
# - VOUCH_IDPS                          (e.g., "google")
# - VOUCH_IDP_GOOGLE_TYPE               (oidc|saml)
# - VOUCH_IDP_GOOGLE_ISSUER
# - VOUCH_IDP_GOOGLE_CLIENT_ID
# - VOUCH_IDP_GOOGLE_CLIENT_SECRET
existingSecret: ""

# Or create a new secret (puts secret values in this file; use existingSecret in production)
secrets: {}
  # VOUCH_JWT_SECRET: ""

# Ingress
ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: auth.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: vouch-tls
      hosts:
        - auth.example.com

# Resources
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi

# Persistence (for SQLite)
persistence:
  enabled: true
  existingClaim: ""
  storageClass: ""
  accessMode: ReadWriteOnce
  size: 1Gi
  mountPath: /data

# Health check configuration
# Liveness uses /health (static, always 200 while the process is up).
# Readiness uses /health/ready (checks the database, 503 when it is unreachable).
healthcheck:
  path: /health
  readinessPath: /health/ready
  initialDelaySeconds: 5
  periodSeconds: 10
  timeoutSeconds: 3
  failureThreshold: 3

Using Kubernetes Secrets

Create secrets for sensitive values:

kubectl create secret generic vouch-secrets \
  --namespace vouch \
  --from-literal=VOUCH_JWT_SECRET='<your-64-character-secret>' \
  --from-literal=VOUCH_IDPS='google' \
  --from-literal=VOUCH_IDP_GOOGLE_TYPE='oidc' \
  --from-literal=VOUCH_IDP_GOOGLE_ISSUER='https://accounts.google.com' \
  --from-literal=VOUCH_IDP_GOOGLE_CLIENT_ID='...' \
  --from-literal=VOUCH_IDP_GOOGLE_CLIENT_SECRET='...'

Then reference in values:

existingSecret: vouch-secrets

Air-Gapped Kubernetes

For air-gapped environments:

  1. Save and transfer the chart:

    helm pull oci://ghcr.io/vouch-sh/charts/vouch-server --version 0.1.0
    # Transfer vouch-server-0.1.0.tgz to air-gapped environment
    
  2. Save and transfer the container image:

    docker pull ghcr.io/vouch-sh/vouch:0.1.0
    docker save ghcr.io/vouch-sh/vouch:0.1.0 -o vouch-0.1.0.tar
    # Transfer and load into your private registry
    
  3. Install from the local chart:

    helm install vouch-server ./vouch-server-0.1.0.tgz \
      --namespace vouch \
      --create-namespace \
      --set image.repository=registry.internal/vouch \
      --values my-values.yaml
    

Upgrading

helm upgrade vouch-server oci://ghcr.io/vouch-sh/charts/vouch-server \
  --version <new-version> \
  --namespace vouch \
  --values my-values.yaml

Health Checks

Vouch exposes two separate endpoints, and they are not interchangeable:

ProbeEndpointBehavior
Liveness/healthReturns 200 with the body ok whenever the process is running. It performs no dependency checks, so it only ever fails if the process is hung or dead — which is exactly what a liveness probe should test.
Readiness/health/readyRuns SELECT 1 against the database. Returns 200 {"status":"ready"}, or 503 {"status":"not_ready","reason":"database"} when the database is unreachable.

Important: point the readiness probe at /health/ready, not /health. A pod whose database connection has failed will keep passing a /health readiness probe and stay in the Service’s endpoint list, sending every request to an instance that cannot serve it.

See Monitoring and Metrics for the full endpoint list and the Prometheus metrics.