On-Premise Configuration¶
This guide covers required and optional environment variables for on-prem deployments.
A full template is available at infra/.env.onprem.example.
Required: Deployment Mode¶
| Variable | Description | Example |
|---|---|---|
DEPLOYMENT_MODE |
Must be single-tenant for on-prem deployments |
single-tenant |
Optional: Tenant Identity¶
| Variable | Required | Default | Description |
|---|---|---|---|
DEFAULT_TENANT_SLUG |
No | default |
Internal identifier for the tenant (URL-safe, lowercase) |
DEFAULT_TENANT_NAME |
No | My Organization |
Your organization's name, displayed in the UI header and reports |
On first boot, KANAP automatically creates a tenant using these values. The defaults work fine for most deployments — you only need to change them if you want a specific organization name to appear in the application.
Required: Admin Credentials¶
| Variable | Description | Example |
|---|---|---|
ADMIN_EMAIL |
Initial admin user email | admin@company.com |
ADMIN_PASSWORD |
Initial admin password (change after first login!) | ChangeMe123! |
JWT_SECRET |
JWT signing key (generate: openssl rand -hex 32) |
64 hex chars |
APP_BASE_URL |
Public URL (used in email links) | https://kanap.company.com |
CORS_ORIGINS |
Comma-separated allowed browser origins (required in production) | https://*.company.com |
Email links: Password reset/invite URLs use APP_BASE_URL (or forwarded host/proto). On-prem should set APP_BASE_URL to the externally reachable URL and configure the reverse proxy to pass Host / X-Forwarded-Proto.
CORS: Configure CORS_ORIGINS to control which browser origins can access the API. The application will fail to start in production if CORS_ORIGINS is not set. Set it to the exact URL users access KANAP from.
Startup validation: The application will refuse to start if JWT_SECRET, DATABASE_URL, or APP_BASE_URL are missing or empty. It also refuses to run if the PostgreSQL role from DATABASE_URL is still SUPERUSER or BYPASSRLS.
Required: Database¶
| Variable | Description | Example |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgres://user:pass@host:5432/kanap?sslmode=require |
Database requirements:
- PostgreSQL 16 or higher (tested minimum; older versions may work but are unsupported)
- Extensions: citext, pgcrypto, uuid-ossp
- User needs CREATE TABLE / ALTER TABLE permissions for migrations
- Recommended: dedicated database
- DATABASE_URL must use a dedicated application role, not postgres or another cluster-admin role
- Recommended: create the app role as NOSUPERUSER NOBYPASSRLS from the start
Database setup (example):
-- 1. Create database and dedicated app role
CREATE DATABASE kanap;
CREATE USER kanap WITH PASSWORD 'secure-password' NOSUPERUSER NOBYPASSRLS;
GRANT ALL PRIVILEGES ON DATABASE kanap TO kanap;
-- 2. Connect to kanap database and enable extensions
\c kanap
CREATE EXTENSION IF NOT EXISTS citext;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- 3. Grant schema permissions (for migrations)
GRANT ALL ON SCHEMA public TO kanap;
If a dedicated application role was initially created with too many privileges, KANAP's first migration will harden it automatically to NOSUPERUSER NOBYPASSRLS. If DATABASE_URL points to a protected cluster-admin role such as postgres, startup fails and you must switch to a dedicated app role.
Required: Storage¶
| Variable | Description | Example |
|---|---|---|
S3_ENDPOINT |
S3-compatible endpoint | https://s3.amazonaws.com |
S3_BUCKET |
Bucket name (must exist) | kanap-files |
S3_REGION |
Region | us-east-1 |
AWS_ACCESS_KEY_ID |
Access key | AKIA... |
AWS_SECRET_ACCESS_KEY |
Secret key | secret |
S3_FORCE_PATH_STYLE |
true for MinIO, false for AWS/R2 |
false |
Bucket requirements:
- Create the bucket before starting KANAP (not auto-created)
- Permissions: s3:PutObject, s3:GetObject, s3:DeleteObject, s3:ListBucket
KANAP uses the AWS SDK v3 S3 client for object storage access; any provider with S3-compatible API behavior is supported.
Tested providers:
- AWS S3 (S3_ENDPOINT=https://s3.amazonaws.com, S3_FORCE_PATH_STYLE=false)
- MinIO (S3_ENDPOINT=http://minio:9000, S3_FORCE_PATH_STYLE=true)
- Cloudflare R2 (https://<account>.r2.cloudflarestorage.com)
- Hetzner (https://<region>.your-objectstorage.com)
Optional: Email via Resend¶
| Variable | Description | Example |
|---|---|---|
RESEND_API_KEY |
Resend API key | re_xxxxx |
RESEND_FROM_EMAIL |
From address | KANAP <noreply@yourdomain.com> |
If not configured, KANAP can still send email through SMTP in single-tenant deployments. If neither Resend nor SMTP is configured, email features are disabled, including user invitations and password reset. See Operations for SQL password reset fallback.
Optional: Email via SMTP (single-tenant / on-prem only)¶
SMTP is supported only in DEPLOYMENT_MODE=single-tenant. Multi-tenant/cloud deployments continue to use Resend.
| Variable | Description | Example |
|---|---|---|
SMTP_HOST |
SMTP server hostname | smtp.company.com |
SMTP_PORT |
SMTP port | 587 |
SMTP_USER |
SMTP username | kanap |
SMTP_PASSWORD |
SMTP password | secret |
SMTP_FROM |
From address | KANAP <noreply@company.com> |
SMTP_SECURE |
true for implicit TLS (465), false for STARTTLS/plain connect (587/25) |
false |
Notes:
- SMTP_USER and SMTP_PASSWORD are optional. Leave both unset for relays that trust the source host/IP.
- If SMTP_SECURE is unset, KANAP defaults to true for port 465 and false otherwise.
- If both SMTP and Resend are configured in single-tenant mode, SMTP takes precedence.
- SMTP_FROM should be an address your SMTP server is allowed to send as.
- If mail is sent outside your network, configure SPF, DKIM, and DMARC on the sender domain through your mail administrator or provider.
Common SMTP profiles
Internal relay without authentication:
Authenticated relay or provider:
SMTP_HOST=smtp.company.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=noreply@company.com
SMTP_PASSWORD=secret
SMTP_FROM=KANAP <noreply@company.com>
Microsoft 365 SMTP submission:
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=noreply@company.com
SMTP_PASSWORD=secret
SMTP_FROM=KANAP <noreply@company.com>
Use the Microsoft 365 profile only if SMTP AUTH is allowed for the mailbox and tenant.
Optional: Entra SSO¶
See the dedicated guide: sso-entra.md.
Optional: Advanced¶
| Variable | Description | Default |
|---|---|---|
LOG_LEVEL |
Logging verbosity (debug, info, warn, error) |
info |
JWT_ACCESS_TOKEN_TTL |
Access token lifetime | 15m |
JWT_REFRESH_TOKEN_TTL |
Refresh token lifetime | 4h |
RATE_LIMIT_ENABLED |
App-level rate limiting toggle | true |
RATE_LIMIT_TRUST_PROXY |
Trust proxy headers for client IP detection | false |
APP_URL |
Base URL for notification email links in multi-tenant mode (tenant slug replaces app). Not needed for on-prem — APP_BASE_URL is used instead. |
https://app.kanap.net |
EMAIL_OVERRIDE |
Redirect all emails to this address (dev/QA only, never in production) | unset |
Full Example (.env)¶
# =============================================================================
# KANAP On-Premise Configuration
# =============================================================================
# DEPLOYMENT MODE (required)
DEPLOYMENT_MODE=single-tenant
# TENANT CONFIGURATION (optional - defaults shown)
DEFAULT_TENANT_SLUG=default
DEFAULT_TENANT_NAME=My Organization
# ADMIN CREDENTIALS (required)
ADMIN_EMAIL=admin@company.com
ADMIN_PASSWORD=ChangeThisPassword123!
# SECURITY (required)
JWT_SECRET=
# APPLICATION URL (required)
APP_BASE_URL=https://kanap.your-domain.com
# DATABASE (required - use a dedicated app role, never postgres)
DATABASE_URL=postgres://kanap:password@your-postgres:5432/kanap?sslmode=require
# STORAGE (required)
S3_ENDPOINT=https://s3.amazonaws.com
S3_BUCKET=kanap-files
S3_REGION=us-east-1
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
S3_FORCE_PATH_STYLE=false # true for MinIO
# EMAIL (optional - Resend)
# RESEND_API_KEY=re_xxxxx
# RESEND_FROM_EMAIL=KANAP <noreply@yourdomain.com>
# EMAIL (optional - SMTP, single-tenant only)
# SMTP_HOST=smtp.company.com
# SMTP_PORT=587
# SMTP_SECURE=false
# SMTP_USER=
# SMTP_PASSWORD=
# SMTP_FROM=KANAP <noreply@company.com>
# ADVANCED (optional - defaults are fine)
# LOG_LEVEL=info
# JWT_ACCESS_TOKEN_TTL=15m
# JWT_REFRESH_TOKEN_TTL=4h
# RATE_LIMIT_ENABLED=true
# RATE_LIMIT_TRUST_PROXY=false
Firewall Rules¶
After initial build, KANAP can run fully air-gapped if email, SSO, and FX rate features are all disabled.
Inbound¶
| Port | Protocol | Purpose |
|---|---|---|
| 443 | TCP | HTTPS — nginx reverse proxy serving the application |
| 80 | TCP | HTTP — redirects to HTTPS |
Outbound — Initial Setup & Build¶
These destinations are only needed during installation and docker build. They can be closed once the application is running.
| Destination | Port | Purpose |
|---|---|---|
github.com |
443 | Clone KANAP source code |
download.docker.com |
443 | Docker APT repository |
dl.min.io |
443 | MinIO binary download |
registry.npmjs.org |
443 | npm dependencies during docker build |
registry-1.docker.io, production.cloudflare.docker.com |
443 | Pull base Docker images (node:20-alpine, nginx:alpine) |
| Ubuntu APT mirrors | 80/443 | System packages (PostgreSQL, nginx, etc.) |
Outbound — Runtime (Conditional)¶
Only required if the corresponding feature is enabled.
| Destination | Port | Purpose | When |
|---|---|---|---|
api.resend.com |
443 | Transactional email | If RESEND_API_KEY is set |
| Your SMTP relay or provider | 25 / 465 / 587 | Transactional email via SMTP | If SMTP_HOST is set |
login.microsoftonline.com |
443 | Entra ID SSO metadata & tokens | If Entra SSO is configured |
graph.microsoft.com |
443 | User profile enrichment | If Entra SSO is configured |
api.worldbank.org |
443 | Annual FX rates | Optional |
v6.exchangerate-api.com |
443 | Spot FX rates | Optional |
Internal (No Firewall Rule Needed)¶
These connections stay on the server — loopback or Docker bridge network only.
| Connection | Port | Notes |
|---|---|---|
| nginx → API container | 8080 | Bound to 127.0.0.1 |
| nginx → Web container | 8081 | Bound to 127.0.0.1 |
| API container → PostgreSQL | 5432 | Via host.docker.internal (Docker bridge 172.16.0.0/12) |
| API container → MinIO | 9000 | Via host.docker.internal |
| MinIO console | 9001 | Local administration only, not exposed externally |
Background Jobs¶
The backend runs scheduled background jobs for email notifications: - Expiration warnings: daily at 08:00 UTC — alerts users about contracts and OPEX items expiring within 30 days. - Weekly review digest: hourly check — sends timezone-aware weekly summaries to users who have opted in.
These jobs require the API to run as a long-running process (not a serverless function). In on-premise mode, APP_BASE_URL is used for notification email links (no subdomain derivation). If no outbound email transport is configured, these jobs skip sending gracefully.