Production Checklist
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 is docker-compose.prod.yml at the repository root, which adds a Caddy sidecar for automatic Let’s Encrypt TLS.
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 -O https://raw.githubusercontent.com/inumansoul/kotauth/main/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.comFor managed databases (RDS, Supabase, Neon), set DB_URL instead of DB_HOST/DB_PORT/DB_NAME. See External Databases.
3. Start
docker compose -f 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 — port 80 must be reachable for the ACME HTTP-01 challenge.
To also enable Redis:
docker compose -f docker-compose.prod.yml --profile redis up -dManual reverse proxy setup
Section titled “Manual reverse proxy setup”If you already have a reverse proxy, use docker-compose.yml (local/eval compose) and proxy to port 8080 directly. Set KAUTH_TRUSTED_PROXY=true and KAUTH_ENV=production in .env.
Caddy (standalone)
Section titled “Caddy (standalone)”auth.yourdomain.com { reverse_proxy kotauth:8080}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)KAUTH_BOOTSTRAP_ADMIN_PASSWORDis set (min 12 chars, mixed case + digit) — or capture the generated password from stdout on first bootKAUTH_TRUSTED_PROXY=trueif behind a reverse proxy (rate limiting usesX-Forwarded-For)DB_URL,DB_USER,DB_PASSWORDpoint to your production PostgreSQL instance —DB_PASSWORDis required and must not be blank- 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:
- RSA private keys — each tenant’s JWT signing key
- SMTP credentials — workspace SMTP passwords
- TOTP secrets — MFA enrollment seeds
- Social provider secrets — Google and GitHub OAuth client secrets
- Audit log HMAC chain —
KAUTH_SECRET_KEYkeys the HMAC-SHA256 that chains audit log rows for tamper detection
If KAUTH_SECRET_KEY is lost, all encrypted data becomes irrecoverable. Back up this value alongside your database backups.
File-based secrets
Section titled “File-based secrets”For production deployments, avoid passing secrets as plain environment variables. Kotauth supports *_FILE sibling variables that read the value from a file at startup:
KAUTH_SECRET_KEY_FILE=/run/secrets/kauth_secret_keyDB_PASSWORD_FILE=/run/secrets/db_passwordCompatible with Docker Swarm secrets, Kubernetes mounted secrets, and systemd LoadCredential=. See Docker for a compose example.
Container hardening
Section titled “Container hardening”The Kotauth Docker image runs with security defaults:
- Non-root user — the process runs as
kotauth(UID 10001, GID 10001), not root - No new privileges —
no-new-privilegessecurity option is set in the compose files - Capability drop —
cap_drop: ALLremoves all Linux capabilities - Read-only filesystem — the runtime filesystem is read-only; only
/tmpis writable
These settings are applied by default in the published compose files. If you run the image with docker run, add the equivalent flags:
docker run -d \ --security-opt no-new-privileges \ --cap-drop ALL \ --read-only \ --tmpfs /tmp \ ghcr.io/inumansoul/kotauth:latestSecurity configuration
Section titled “Security configuration”Set the admin password. As of v1.14.1, there are no hardcoded default credentials. Set KAUTH_BOOTSTRAP_ADMIN_PASSWORD in your environment before first boot — minimum 12 characters with uppercase, lowercase, and a digit. If unset, a random password is generated and printed to stdout once.
Enable trusted proxy. If Kotauth runs behind a reverse proxy, set KAUTH_TRUSTED_PROXY=true so rate limiting uses the real client IP from X-Forwarded-For. Leave it false when directly exposed.
Configure SMTP. Required for email verification, password resets, magic links, Email OTP, and user invitations. 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.
Provision API keys via environment. For automated deployments, use KAUTH_BOOTSTRAP_API_KEYS to create API keys idempotently on startup. See Environment Variables and CLI Commands.
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-compose.prod.yml pulldocker compose -f 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, edit the image tag in your compose file:
image: ghcr.io/inumansoul/kotauth:1.19.2Backups
Section titled “Backups”Database-level backup
Section titled “Database-level 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).sqlTenant-level backup
Section titled “Tenant-level backup”Kotauth also provides encrypted tenant snapshots via the CLI and admin API. These use PBKDF2 + AES-256-GCM and are portable across Kotauth instances. See Backup & Restore for full documentation.
Audit log integrity
Section titled “Audit log integrity”The audit log uses an HMAC chain for tamper detection. Each row carries prev_hash and row_hash computed via HMAC-SHA256 keyed by KAUTH_SECRET_KEY. Verify the chain at any time:
docker compose exec kauth java -jar kauth.jar cli verify-audit-chain --tenant=my-workspaceSee CLI Commands for details.
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.