Deployment Guide

Ship Jovvix to your own infrastructure. This guide walks through two deployment paths — a single-command Docker Compose setup for local and small-scale hosting, and a production-grade Helm + Kubernetes workflow for scalable cloud deployments. Pick the path that fits your stack.

Two ways to deploy

Option 1

Docker Compose

Spin up every Jovvix service — API, Web, Postgres, Redis, Kratos, MinIO, and Mailpit — with a single command. Ideal for local development or a compact single-host deployment.

  • One command to launch the full stack
  • No Kubernetes cluster required
  • Best for local or small deployments
Jump to Docker Compose
Option 2

Helm + Kubernetes

Deploy each component via Helm charts — Bitnami for Postgres/Redis, Ory for Kratos, and Improwised's Polymorphic chart for the Jovvix API and UI. Production-grade and horizontally scalable.

  • Scalable, cloud-native footprint
  • Helm-managed upgrades and rollbacks
  • Ingress, TLS, and secrets handled natively
Jump to Helm setup

Using docker-compose

Drop this docker-compose.yml into the project root. It provisions the database, cache, identity service, object storage, and mail catcher alongside the Jovvix API and Web services.

docker-compose.yml
version: "3.8"
services:
  db:
    image: postgres:15.2-alpine
    container_name: jovvix-db
    environment:
      POSTGRES_USER: jovvix
      POSTGRES_PASSWORD: jovvix
      POSTGRES_DB: jovvix
      POSTGRES_MULTIPLE_DATABASES: '"kratos-db"'
    ports:
      - 5432:5432
    volumes:
      - pgdata:/var/lib/postgresql/data

  migration:
    build:
      context: ./api/
      dockerfile: Dockerfile
    restart: on-failure
    depends_on:
      - db
    env_file:
      - ./api/.env.docker
    command: ["./jovvix", "migrate", "up"]

  redis:
    image: redis:latest
    restart: always
    ports:
      - "6379:6379"
    env_file:
      - ./api/.env.docker
    volumes:
      - redisdata:/root/redis
      - ./api/redis/redis.conf:/usr/local/etc/redis/redis.conf

  api:
    image: jovvix-api:0.0.1
    ports:
      - "3000:3000"
    restart: on-failure
    env_file:
      - ./api/.env.docker
    depends_on:
      - db
      - migration
      - redis
      - kratos
    command: ["./jovvix", "api"]

  web:
    image: jovvix-web:0.0.1
    ports:
      - "5000:5000"
    restart: on-failure
    env_file:
      - ./app/.env.docker
    depends_on:
      - api

  kratos_migrate:
    image: oryd/kratos:v1.0.0
    environment:
      - DSN=postgres://jovvix:jovvix@db:5432/jovvix?sslmode=disable&search_path=kratos
      - LOG_LEVEL=trace
    command: migrate sql -e --yes
    restart: on-failure
    depends_on:
      - db

  kratos:
    image: oryd/kratos:v1.0.0
    ports:
      - "4433:${SERVE_PUBLIC_PORT:-4433}" # public
      - "4434:${SERVE_ADMIN_PORT:-4434}" # admin
    env_file:
      - ./api/.env.kratos
    command: serve -c /etc/config/kratos/kratos.yml --watch-courier
    restart: always
    depends_on:
      - db
      - kratos_migrate
    volumes:
      - "./api/pkg/kratos:/etc/config/kratos"

  minio:
    image: quay.io/minio/minio
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - ~/minio/data:/data
    environment:
      MINIO_ROOT_USER: ROOTNAME
      MINIO_ROOT_PASSWORD: CHANGEME123
    command: server /data --console-address ":9001"

  mailpit:
    image: axllent/mailpit
    container_name: mailpit
    restart: unless-stopped
    volumes:
      - ./data:/data
    ports:
      - 8025:8025
      - 1025:1025
    environment:
      MP_MAX_MESSAGES: 5000
      MP_DATABASE: /data/mailpit.db
      MP_SMTP_AUTH_ACCEPT_ANY: 1
      MP_SMTP_AUTH_ALLOW_INSECURE: 1
      MP_SENDMAIL_SMTP_ADDR: mailpit:1025
      MP_SENDMAIL_FROM: [email protected]

volumes:
  pgdata:
  redisdata:
Then run
docker-compose up

Prerequisites (Helm)

  • Linux VM or Cloud Instance

    Any Linux host will do — a local VM, a cloud VM, or a managed Kubernetes control plane.

  • Internet Connectivity

    Required to pull charts and container images from Bitnami, ORY, and the Improwised registry.

  • CLI Tools

    curl, helm, and kubectl on your PATH — plus access to a running Kubernetes cluster.

Helm setup — step by step

Step 01

Add Bitnami Helm Repository

Bitnami ships the PostgreSQL and Redis charts that back the Jovvix API.

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
Step 02

Create Namespace

All Jovvix workloads land in a dedicated namespace.

kubectl create namespace jovixx
Step 03

Install PostgreSQL (Bitnami)

Primary relational store for the API and Kratos identity data.

Install
helm install my-postgres bitnami/postgresql \
  --namespace jovixx \
  --set auth.postgresPassword=adminpassword \
  --set primary.persistence.size=1Gi
Verify
kubectl get pods -n jovixx -l app.kubernetes.io/name=postgresql
Step 04

Install Redis using Bitnami chart

In-memory cache and pub/sub layer for realtime quiz events.

Install
helm install my-redis bitnami/redis \
  --namespace jovixx \
  --set auth.enabled=true \
  --set auth.password=adminredis \
  --set master.persistence.size=1Gi
Verify
kubectl get pods -n jovixx -l app.kubernetes.io/name=redis
Step 05

Access PostgreSQL & Redis

Smoke-test the datastores from inside the cluster before wiring the app.

PostgreSQL client
kubectl run pg-client --rm -i --tty --namespace jovixx \
  --image bitnami/postgresql --env="PGPASSWORD=adminpassword" \
  --command -- psql -h my-postgres-postgresql -U postgres
Redis client
kubectl run redis-client --rm -i --tty --namespace jovixx \
  --image bitnami/redis --command -- redis-cli -h my-redis-master -a adminredis
Step 06

Add ORY Helm Repo

Pull the Kratos chart from ORY's official Helm repository.

helm repo add ory https://k8s.ory.sh/helm/charts
helm repo update

Configure & Install ORY Kratos

Step 07

Create Kratos ConfigMap

Apply this ConfigMap to the jovvix namespace. It carries both the identity schema and the Kratos configuration.

configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: jovvix-kratos-config
  namespace: jovvix
data:
  identity.schema.json: |
    {
      "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
      "$schema": "http://json-schema.org/draft-07/schema#",
      "title": "Person",
      "type": "object",
      "properties": {
      "traits": {
          "type": "object",
          "properties": {
              "email": {
                  "type": "string",
                  "format": "email",
                  "title": "E-Mail",
                  "minLength": 3,
                  "ory.sh/kratos": {
                      "credentials": {
                          "password": {
                              "identifier": true
                          }
                      },
                      "verification": {
                          "via": "email"
                      },
                      "recovery": {
                          "via": "email"
                      }
                  }
              },
              "name": {
                  "type": "object",
                  "properties": {
                      "first": {
                          "title": "First Name",
                          "type": "string"
                      },
                      "last": {
                          "title": "Last Name",
                          "type": "string"
                      }
                  }
              }
          },
          "required": [
              "email"
          ],
          "additionalProperties": false
        }
      }
    }
  kratos.yaml: |
    serve:
      public:
        base_url: https://jovvix.i8d.in
        port: 4433
        cors:
          enabled: true
          allowed_origins:
            - https://jovvix.i8d.in
          allowed_methods:
            - POST
            - GET
            - PUT
            - PATCH
            - DELETE
          allowed_headers:
            - Authorization
            - Cookie
            - Content-Type
          exposed_headers:
            - Content-Type
            - Set-Cookie
          allow_credentials: true
      admin:
        base_url: https://jovvix.i8d.in
        port: 4434
    selfservice:
      allowed_return_urls:
      - https://jovvix.i8d.in
      default_browser_return_url: https://jovvix.i8d.in
      flows:
        error:
          ui_url: https://jovvix.i8d.in/error
        login:
          after:
            default_browser_return_url: https://jovvix.i8d.in/api/v1/kratos/auth
            hooks:
              - hook: require_verified_address
          lifespan: 10m
          ui_url: https://jovvix.i8d.in/account/login
        logout:
          after:
            default_browser_return_url: https://jovvix.i8d.in/login
        recovery:
          enabled: true
          ui_url: https://jovvix.i8d.in/recovery
          use: code
        registration:
          after:
            default_browser_return_url: https://jovvix.i8d.in/api/v1/kratos/auth
            oidc:
              hooks:
               - hook: session
            password:
              hooks:
               - hook: session
               - hook: show_verification_ui
          lifespan: 10m
          ui_url: https://jovvix.i8d.in/account/register
        settings:
          privileged_session_max_age: 15m
          required_aal: highest_available
          ui_url: https://jovvix.i8d.in/settings
        verification:
          after:
            default_browser_return_url: https://jovvix.i8d.in/api/v1/kratos/auth
          enabled: true
          ui_url: https://jovvix.i8d.in/verification
          use: code
      methods:
        code:
          enabled: true
        link:
          enabled: true
        lookup_secret:
          enabled: true
        oidc:
          config:
            base_redirect_uri: https://jovvix.i8d.in
            providers:
            - auth_url: https://accounts.google.com/o/oauth2/v2/auth
              client_id: <GOOGLE_CLIENT_ID>
              client_secret: <GOOGLE_CLIENT_SECRET>
              id: google
              issuer_url: https://accounts.google.com
              mapper_url: base64://<BASE64-JASONNATE_SCHEMA>
              provider: google
              scope:
              - openid
              - email
              - profile
              token_url: https://www.googleapis.com/oauth2/v4/token
          enabled: true
        password:
          enabled: true
        totp:
          config:
            issuer: Kratos
          enabled: true
    ciphers:
      algorithm: 
    cookies:
      domain: jovvix.i8d.in
      path: /
      same_site: Lax
    courier:
      smtp: {
        from_address: 
      }
    hashers:
      algorithm: bcrypt
      bcrypt:
        cost: 8
    identity:
      default_schema_id: default
      schemas:
      - id: default
        url: file:///etc/config/identity.schema.json
    log:
      format: json
      leak_sensitive_values: true
      level: warning
Also create a Secret

Fill in the DSN, cipher, cookie, default secrets, and SMTP connection URI before applying.

apiVersion: v1
kind: Secret
metadata:
  name: jovvix-app-kratos
  namespace: jovvix-app
stringData:
  dsn:
  secretsCipher: 
  secretsCookie: 
  secretsDefault: 
  smtpConnectionURI:
Step 08

Install ORY Kratos

Create a kratos-values.yaml with the ingress, secret, and migration settings below, then install the chart.

kratos-values.yaml
   ingress:
      admin:
        enabled: true
        className: "nginx"
        hosts:
          - host: jovvix.i8d.in
            paths:
              - path: /admin/identities
                pathType: ImplementationSpecific
                servicePort: http 
      public:
        enabled: true
        className: "nginx"
        hosts:
          - host: jovvix.i8d.in
            paths:
              - path: /self-service
                pathType: ImplementationSpecific
                servicePort: http
              - path: /sessions
                pathType: ImplementationSpecific
                servicePort: http
              
    secret:
      enabled: false
      nameOverride: jovvix-app-kratos
    kratos:
      automigration:
        enabled: true
      config:
        courier:
          smtp:
            connection_uri:
Install
helm install kratos ory/kratos -f kratos-values.yaml -n jovixx
Final Step

Deploy Jovvix via Polymorphic chart

Use the improwised/polymorphic Helm chart to roll out the Jovvix UI, API, and the one-shot DB migration job. Create the appropriate ConfigMap and Secret resources first, then apply this values file.

Add the repo
helm repo add improwised https://improwised.github.io/charts/
values.yaml
services:
  - name: ui
    image:
      repository: improwised/jovvix-app
      tag: 1.0.0
    envFrom:
      - configMapRef:
          name: jovvix-app
    healthcheck:
      enabled: false
    ports:
      - name: http
        containerPort: 5000
        protocol: TCP
    resources:
      limits:
        memory: 600Mi
      requests:
        cpu: 100m
        memory: 600Mi
    service:
      enabled: true
      type: ClusterIP
      ports:
        - name: http
          port: 5000
          targetPort: http
          protocol: TCP
    ingress:
      enabled: true
      className: nginx
      hosts:
        - host: jovvix.i8d.in
          paths:
            - path: /
              pathType: Prefix
              servicePort: http
  - name: api
    image:
      repository: improwised/jovvix-api
      tag: 1.0.0
    envFrom:
      - configMapRef:
          name: jovvix-api
      - secretRef:
          name: jovvix-api
    healthcheck:
      enabled: true
      type: httpGet
      path: /api/healthz
      port: http
      initialDelaySeconds: 6
      periodSeconds: 6
    resources:
      limits:
        cpu: 1000m
        memory: 4Gi
      requests:
        cpu: 500m
        memory: 4Gi
    ports:
      - name: http
        containerPort: 3300
        protocol: TCP
    service:
      enabled: true
      type: ClusterIP
      ports:
        - name: http
          port: 3300
          targetPort: http
          protocol: TCP
    command: ["/bin/sh", "-c"]
    args: ["./api api"]
    ingress:
      enabled: true
      className: nginx
      hosts:
        - host: jovvix.i8d.in
          paths:
            - path: /api
              pathType: Prefix
              servicePort: http
jobs:
  - name: migration
    image:
      repository: improwised/jovvix-api
      tag: 1.0.0
    envFrom:
      - configMapRef:
          name: jovvix-api
      - secretRef:
          name: jovvix-api # To keep the container running
    command: ["/bin/sh", "-c"]
    args: ["./api migrate up"]
    annotations:
      # This is what defines this resource as a hook. Without this line, the
      # job is considered part of the release.
      # after completing migration job will disappear
      "helm.sh/hook": post-install,post-upgrade
      "helm.sh/hook-weight": "-5"
      "helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation

The migration job is registered as a Helm hook — it runs after each install/upgrade and is cleaned up on success.

Deployment Notes

  • Swap the domain: Every sample uses jovvix.i8d.in — replace it everywhere (Kratos config, ingress hosts, return URLs) with your own FQDN before applying.

  • Fill in secrets: Generate strong values for secretsCipher, secretsCookie, and secretsDefault. Empty Kratos secrets will crash the pod on startup.

  • Mind the namespaces: The sample mixes jovixx and jovvix / jovvix-app. Pick one consistently across ConfigMaps, Secrets, and helm -n flags.

  • Image tags: The values file pins improwised/jovvix-app and improwised/jovvix-api to 1.0.0 — bump to the release you want to ship.

  • SMTP / email: Kratos needs a working SMTP connection URI for verification and recovery flows. Use Mailpit for dev and a real provider in production.

  • TLS & ingress: The ingress samples use className: nginx with plain HTTP. Add your cert-manager annotations and TLS blocks before going live.