Skip to content

Going to Production

import { Aside } from ‘@astrojs/starlight/components’;

  • Postgres running outside the cluster (Neon, RDS, Cloud SQL) with SSL
  • DEV_AUTH_TOKEN is empty — never set this in production
  • Auth0 SPA and API configured with your production domain
  • TLS enabled on the ingress (cert-manager + Let’s Encrypt)
  • Redis persistence enabled (AOF or RDB snapshot)
  • Resource requests and limits set on all pods
  • Liveness and readiness probes verified (/healthz, /readyz)
  • Prometheus scraping enabled if running kube-prometheus-stack

Use a managed Postgres service. The DATABASE_URL must include sslmode=require.

Run migrations as a pre-install/pre-upgrade Helm hook or a one-shot Job:

Terminal window
kubectl run migrate --rm -it \
--image=ghcr.io/play-quibble/trivia-api:latest \
--restart=Never \
--env="DATABASE_URL=${DATABASE_URL}" \
-- /app/api migrate up

The in-cluster Redis StatefulSet (enabled by default in the Helm chart) is sufficient for most deployments. For high availability, replace it with a managed Redis (DigitalOcean Managed Redis, Elasticache, Memorystore).

Never put secrets in values.yaml. Use one of:

  • Kubernetes Secret created out-of-band (kubectl create secret) and referenced via api.existingSecret
  • External Secrets Operator syncing from AWS Secrets Manager, GCP Secret Manager, or Vault
  • Sealed Secrets for GitOps-safe encrypted secret manifests

The API is stateful per WebSocket connection. For multiple replicas, enable Redis Pub/Sub fan-out (planned feature) and add sticky sessions to the ingress:

nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "quibble-session"

The API exposes Prometheus metrics at /metrics. Add a ServiceMonitor if you’re running the kube-prometheus-stack:

serviceMonitor:
enabled: true
namespace: monitoring

The recommended setup uses ingress-nginx with cert-manager:

ingress:
enabled: true
className: nginx
host: quibble.yourdomain.com
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
enabled: true
issuer: letsencrypt-prod

All traffic routes through one ingress:

  • /api/* → API service
  • /* → Web service