self-hosting
Self-hosting overview
Run Kutrion yourself as a single Docker Compose unit — the modular monolith plus Postgres and Redis, env-driven, no Kubernetes.
Updated 22 सित॰ 2026
Kutrion self-hosts as a single modular-monolith unit orchestrated with Docker Compose. There is
one application process — not a fleet of services — so the supported deployment unit is Compose, not
Helm or Kubernetes. The same image you run in dev runs in staging and on-prem; only .env changes.
What runs
The stack is three containers defined in deploy/onprem/docker-compose.yml:
| Service | Image | Purpose |
|---|---|---|
app | kutrion/app (built from this repo) | API + web portal on :4000; admin/platform plane on :4100 |
db | postgres:16 | tenant data, isolated per organization |
cache | redis:7 | sessions, rate limits, step-up proofs |
The app container serves the web portal same-origin with the API on :4000, and hosts the
admin/platform plane on a second port, :4100. There is no separate frontend service, no API gateway,
and no orchestrator to operate — one container is the whole application tier.
Env-driven, one image everywhere
Every difference between environments lives in .env, read automatically by docker compose. The
image is built once and carries no environment-specific configuration. Pointing the same image at a
different database, public URL, key provider, or AI backend is purely a matter of editing .env and
recreating the container — there is no rebuild for a config change. This is what makes the dev, staging,
and on-prem deployments the same artifact. See the
Configuration reference for the full set of variables.
Migrations run on boot
The app container applies database migrations on every boot. They are tracked in
schema_migrations and idempotent, so docker compose up after an image upgrade is a complete deploy
— there is no separate migration step to run by hand. Migrations are forward-only; take a backup
before upgrading (see Backups and recovery).
Container logs are bounded
Docker’s default json-file driver has no size limit, so a compose stack that says nothing about
logging writes one log file per service that is never rotated and grows until the disk fills. The
bundled compose file sets an explicit ceiling on all three services, reached by rotation — at the
limit the oldest file is dropped and the container keeps logging, so you lose history, not
availability.
| service | rotates at | files kept | ceiling |
|---|---|---|---|
app | APP_LOG_MAX_SIZE (50m) | APP_LOG_MAX_FILE (5) | 250 MiB |
db | INFRA_LOG_MAX_SIZE (10m) | INFRA_LOG_MAX_FILE (3) | 30 MiB |
cache | INFRA_LOG_MAX_SIZE (10m) | INFRA_LOG_MAX_FILE (3) | 30 MiB |
310 MiB for the whole stack. Change any of the four in .env and recreate the containers; you never
need to edit the compose file. Confirm what the daemon applied with
docker inspect -f '{{.HostConfig.LogConfig.Config}}' <container>. If you need logs kept longer than
the ceiling allows, ship them off the host rather than raising the ceiling until the disk is the
limit again.
TLS and the admin plane
The bundle serves plain HTTP on :4000. Terminate TLS in a reverse proxy in front of it — WebAuthn
and secure cookies require HTTPS, and localhost is the only exempt origin. Proxy :4000 for the
tenant app.
The admin/platform plane on :4100 binds to loopback by default (ADMIN_BIND=127.0.0.1). Reach it over
an SSH tunnel or a trusted private interface; never expose it on the public internet, and never
proxy it publicly.
Where to go next
- Install with Docker Compose — the connected install procedure.
- Air-gapped install — build a bundle and install with no network.
- Licensing and activation — the offline
.vtliclicense. - Backups and recovery — what to back up and how to recover.
- Upgrading — connected and air-gapped upgrades.
- Self-hosting FAQ — common questions, answered honestly.
- Deciding whether to self-host at all? See SaaS vs self-hosted.