Production Checklist
import { Aside } from ‘@astrojs/starlight/components’;
Requirements
Section titled “Requirements”Minimum hardware: 512 MB RAM, 1 vCPU.
PostgreSQL 14 or newer is required. Kotauth manages its own schema via Flyway migrations — no manual DDL needed.
TLS is mandatory. Kotauth does not handle TLS directly — it expects a reverse proxy to terminate it. Set KAUTH_ENV=production and ensure KAUTH_BASE_URL starts with https://. The server refuses to start otherwise.
Docker Compose production stack
Section titled “Docker Compose production stack”The fastest path to a production deployment is the bundled docker/docker-compose.prod.yml, which adds a Caddy sidecar to the base stack. Caddy handles automatic TLS certificate provisioning and renewal via Let’s Encrypt — no manual certificate management required.
Prerequisites: a domain pointing to your server and ports 80/443 open on the host firewall.
1. Get the files
mkdir kotauth && cd kotauth
curl --create-dirs -o docker/docker-compose.yml \ https://raw.githubusercontent.com/inumansoul/kotauth/main/docker/docker-compose.ymlcurl --create-dirs -o docker/docker-compose.prod.yml \ https://raw.githubusercontent.com/inumansoul/kotauth/main/docker/docker-compose.prod.ymlcurl --create-dirs -o docker/Caddyfile \ https://raw.githubusercontent.com/inumansoul/kotauth/main/docker/Caddyfilecurl -o .env.example \ https://raw.githubusercontent.com/inumansoul/kotauth/main/.env.examplecp .env.example .env2. Fill in .env for production
KAUTH_BASE_URL=https://auth.yourdomain.comKAUTH_ENV=productionKAUTH_SECRET_KEY= # openssl rand -hex 32
DB_NAME=kotauth_dbDB_USER=kotauthDB_PASSWORD= # strong unique password
DOMAIN=auth.yourdomain.comACME_EMAIL=you@yourdomain.com3. Start
docker compose -f docker/docker-compose.yml -f docker/docker-compose.prod.yml up -dThis brings up three services: db (PostgreSQL with a persistent volume), app (Kotauth from GHCR), and caddy (automatic TLS). Caddy obtains the certificate on first startup — this requires port 80 to be reachable for the ACME HTTP-01 challenge.
Manual reverse proxy setup
Section titled “Manual reverse proxy setup”If you already have a reverse proxy running on the host, skip the Caddy overlay and proxy to port 8080 directly.
Caddy (standalone)
Section titled “Caddy (standalone)”auth.yourdomain.com { reverse_proxy kotauth:8080}That’s the entire Caddyfile. Caddy provisions and renews the certificate automatically.
server { listen 443 ssl; server_name auth.yourdomain.com;
ssl_certificate /etc/ssl/certs/fullchain.pem; ssl_certificate_key /etc/ssl/private/privkey.pem;
location / { proxy_pass http://kotauth:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}Traefik
Section titled “Traefik”# Add these labels to the app service in your compose filelabels: - "traefik.enable=true" - "traefik.http.routers.kotauth.rule=Host(`auth.yourdomain.com`)" - "traefik.http.routers.kotauth.entrypoints=websecure" - "traefik.http.routers.kotauth.tls.certresolver=letsencrypt" - "traefik.http.services.kotauth.loadbalancer.server.port=8080"Environment checklist
Section titled “Environment checklist”Before starting in production, verify:
KAUTH_ENV=productionKAUTH_BASE_URLstarts withhttps://KAUTH_SECRET_KEYis a freshly generated 32+ byte hex string (usejava -jar kauth.jar cli generate-secret-keyoropenssl rand -hex 32)DB_URL,DB_USER,DB_PASSWORDpoint to your production PostgreSQL instance- Database user has
CREATE,SELECT,INSERT,UPDATE,DELETEpermissions (required for Flyway migrations on first boot) - Port 5432 is blocked on the host firewall — the database should never be publicly reachable
Encryption at rest
Section titled “Encryption at rest”KAUTH_SECRET_KEY derives the AES-256-GCM key used to encrypt sensitive data stored in the database. This currently covers:
- RSA private keys — each tenant’s JWT signing key is encrypted before being persisted. Existing plaintext keys are automatically migrated to encrypted form on first startup after upgrading to v1.3.0+.
- SMTP credentials — passwords for workspace SMTP configurations.
- TOTP secrets — MFA enrollment seeds.
If KAUTH_SECRET_KEY is lost, all encrypted data becomes irrecoverable. Back up this value alongside your database backups.
HTTP compression and caching
Section titled “HTTP compression and caching”Kotauth enables gzip and deflate compression on all HTTP responses. Static assets (CSS bundles, JavaScript, Swagger UI files) are served with long-lived Cache-Control headers. No additional reverse proxy configuration is needed for compression — Ktor handles it at the application level.
Security configuration
Section titled “Security configuration”After startup, complete these steps in the admin console:
Change the master workspace admin password. Default credentials are printed in the startup log on first boot and must be rotated immediately.
Configure SMTP. Required for email verification and password resets. Without it, users cannot verify their email or reset forgotten passwords. Set this up under Settings → SMTP in each workspace.
Review password policy. The default (minimum 8 characters) may not meet your requirements. Tighten it under Settings → Security.
Set the MFA policy. Decide whether MFA should be optional, required, or required_for_admins. For sensitive workspaces, required is the safe default.
Create workspaces with meaningful slugs. The workspace slug appears in every URL and cannot be changed after creation. Choose something permanent (e.g. my-product, not test-1).
Upgrading
Section titled “Upgrading”Kotauth uses Flyway for schema migrations. Upgrades are handled automatically on startup — pull the new image and restart:
docker compose -f docker/docker-compose.yml -f docker/docker-compose.prod.yml pulldocker compose -f docker/docker-compose.yml -f docker/docker-compose.prod.yml up -dFlyway runs any pending migrations before the server begins accepting traffic. Always back up the database before upgrading between major versions.
To pin to a specific version rather than tracking latest, edit docker/docker-compose.yml:
# change:image: ghcr.io/inumansoul/kotauth:latest# to:image: ghcr.io/inumansoul/kotauth:1.0.1Database backup
Section titled “Database backup”Kotauth’s entire state lives in PostgreSQL. Back up regularly using standard PostgreSQL tools:
# Docker Compose stackdocker exec kotauth-db pg_dump -U kotauth kotauth_db > backup_$(date +%Y%m%d).sql
# External databasepg_dump -h your-db-host -U kotauth kotauth_db > backup_$(date +%Y%m%d).sqlRestore:
cat backup_20260101.sql | docker exec -i kotauth-db psql -U kotauth -d kotauth_dbThere is no Kotauth-specific backup procedure — standard PostgreSQL backup and restore works fully.
Monitoring
Section titled “Monitoring”Kotauth emits structured JSON logs to stdout. Key fields in each log line:
| Field | Description |
|---|---|
level | INFO, WARN, ERROR |
message | Human-readable description |
tenantSlug | Workspace context (MDC) |
requestId | X-Request-Id header value for tracing |
duration | Request duration in milliseconds |
Route these logs to your observability stack (Loki, CloudWatch, Datadog, etc.). The audit log API is the authoritative source for security events — do not rely on application logs for compliance.