Self-host KRINT
Run KRINT from the pre-built image, on either registry:
- GitHub Container Registry -
ghcr.io/pianonic/krint:latest - Docker Hub -
pianonic/krint:latest
The two are identical; use whichever you prefer. Examples below use the GHCR tag.
You need a Linux/Windows host with Docker + Compose v2, and a directory to keep state in.
Pick your path
| You have… | Do this |
|---|---|
| Your own OIDC provider (Pocket ID, Authentik, Auth0, Keycloak…) | Quickstart below - two files, no clone. |
| Nothing yet, want zero-config auth | Bundled Keycloak - clone the repo. |
| A single machine, just you | The desktop app - SQLite, built-in login, no Docker auth setup. |
Quickstart
Drop these three files in an empty folder and run docker compose up -d. Open http://localhost:5000. State lives in ./db/, ./backups/, and /data/krint/ (provisioned instance data).
compose.yml
services:
krint:
image: ghcr.io/pianonic/krint:latest # or pianonic/krint:latest (Docker Hub)
container_name: krint
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway" # how krint reaches provisioned DBs
depends_on:
db:
condition: service_healthy
ports:
- "5000:8080"
environment:
Database__Provider: "Postgres"
ConnectionStrings__KrintDatabase: "Host=db;Port=5432;Database=krint;Username=postgres;Password=${POSTGRES_PASSWORD}"
Vault__MasterKey: ${KRINT_VAULT_KEY}
Krint__PublicUrl: ${KRINT_PUBLIC_URL} # login redirect + CORS are derived from this
Cors__AllowedOrigins__0: ${KRINT_PUBLIC_URL}
Oidc__Authority: ${KRINT_OIDC_AUTHORITY}
Oidc__ClientId: ${KRINT_OIDC_CLIENT_ID}
Oidc__Scope: "openid profile email roles"
Oidc__RequireHttpsMetadata: "true"
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Windows: //var/run/docker.sock
- ./backups:/app/backups
- ./krint.yaml:/app/krint.yaml:ro # port ranges, storage, nodes, declarative instances
- /data/krint:/data/krint # provisioned instance data (HostFolder storage)
db:
image: postgres:18.4
container_name: krint-db
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: krint
volumes:
- ./db:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d krint"]
interval: 2s
timeout: 3s
retries: 30.env
POSTGRES_PASSWORD=change-me
KRINT_VAULT_KEY=GENERATE_ME # openssl rand -base64 32
# The public URL KRINT is served on. The login redirect URI and CORS origin are derived from it.
KRINT_PUBLIC_URL=http://localhost:5000
# Your OIDC provider:
KRINT_OIDC_AUTHORITY=https://auth.example.com/realms/krint
KRINT_OIDC_CLIENT_ID=krintkrint.yaml - the host-port ranges KRINT allocates from per engine. Copy as-is:
krint:
storage:
mode: HostFolder # provisioned data lives in /data/krint on the host (set mode: Volume for named docker volumes)
host_path: /data/krint
port_ranges:
postgres: 30000-30099
timescaledb: 30100-30199
mysql: 30200-30399
mariadb: 30400-30599
mssql: 30600-30799
mongo: 30800-30999
redis: 31000-31199
cockroachdb: 31200-31399
clickhouse: 31400-31599
cassandra: 31600-31799
couchdb: 32000-32199
neo4j: 33200-33399
qdrant: 34000-34199
valkey: 34200-34399
seaweedfs: 34400-34599
pgvector: 34600-34799
azurite: 34800-34999WARNING
With HostFolder storage, /data/krint must be writable by the engine containers, which run as fixed UIDs. If a provision fails right after the container starts, chown the folder (e.g. sudo chown -R 999:999 /data/krint for Postgres) - or switch to mode: Volume to let Docker manage it.
On your IdP, register KRINT as a public client (PKCE, no secret) with redirect URI http://localhost:5000/*. That's it - the rest is reference below.
No OIDC provider? Bundled Keycloak
For zero-config auth, clone the repo. Its compose.yml ships Keycloak with a ready-to-import realm:
git clone https://github.com/PianoNic/KRINT.git && cd KRINT
cp .env.example .env # edit before first start
docker compose up -d # postgres + keycloak + krintFirst boot imports the krint realm (~30-60s). Then open Keycloak at http://localhost:8080, log in as the bootstrap admin from .env, switch to the krint realm, and add a user under Users → Add user. Log in to KRINT at http://localhost:5000.
Configuration reference
Environment variables
Set these on the krint service (the Quickstart pulls them from .env).
| Variable | What it does |
|---|---|
Vault__MasterKey | AES-256 key for the secrets vault. 32 random bytes, base64 (openssl rand -base64 32). Encrypts every instance password. |
ConnectionStrings__KrintDatabase | KRINT's own metadata DB. Postgres: Host=db;Port=5432;Database=krint;Username=postgres;Password=…. SQLite: Data Source=/data/krint.db. |
Database__Provider | Postgres or Sqlite (default). Picks the metadata store. |
Oidc__Authority | Public IdP discovery URL. Must match the issuer in <authority>/.well-known/openid-configuration byte-for-byte (scheme, port, trailing slash). |
Oidc__ClientId | Client ID registered on the IdP (public/PKCE). |
Oidc__RedirectUri / …PostLogoutRedirectUri | Return URL after login/logout. Must be registered on the IdP, keep the trailing slash. |
Oidc__Scope | openid profile email roles (roles optional). |
Oidc__RequireHttpsMetadata | true (set false only for a plain-HTTP IdP). |
Cors__AllowedOrigins__0 | Browser origin allowed to call the API - KRINT URL without trailing slash. Add more as __1, __2. |
Krint__PublicUrl | Public URL this control plane is served on (e.g. https://krint.example.com). Drives the Add-node compose, and backs the OIDC redirect + CORS when those aren't set explicitly. Required to add nodes - it must be reachable from each node's host (not localhost); the Add-node dialog refuses to generate a compose until it's set. |
Backup__Directory | Where dumps are written. Optional - defaults to /app/backups (the path the compose bind-mounts). |
Docker__Endpoint | Docker daemon URI, e.g. unix:///var/run/docker.sock. Optional - auto-detected (Unix socket / Windows named pipe) when unset. |
DANGER
Never lose or rotate Vault__MasterKey once you have data. There's no recovery and no key-rotation flow.
SQLite instead of Postgres
Drop the db service and point KRINT at a file on a mounted volume:
environment:
Database__Provider: "Sqlite"
ConnectionStrings__KrintDatabase: "Data Source=/data/krint.db"
volumes:
- ./db:/dataRemove depends_on: db. The file must be on a volume or it's wiped on every recreate.
Instance data on a host folder
By default each provisioned instance gets a named Docker volume. To see the raw files instead, set in krint.yaml:
krint:
storage:
mode: HostFolder
host_path: /data/krint # path on the Docker HOSTNew provisions then bind-mount ${host_path}/${containerName}. Two gotchas: engines run as a fixed UID and need write access (chown 999:999 … for Postgres), and delete-cleanup only works if host_path is mounted into the krint container at the same path.
OIDC provider setup & quirks
KRINT's SPA uses Authorization Code Flow + PKCE, so register a public client (no secret). Configure on the IdP:
| Setting | Value |
|---|---|
| Client type | Public (PKCE) |
| Redirect / post-logout URI | http://localhost:5000/* (or your public URL) |
| Web origins / CORS | KRINT origin, no trailing slash |
| Scopes | openid profile email (roles optional) |
- Pocket ID - toggle Public Client; allow your user/group.
*wildcards work. - Authentik - use the Provider's issuer (
/application/o/<slug>/) as the authority, not the Application URL. - Auth0 - authority is
https://<tenant>.auth0.com/(trailing slash); app type SPA. - External Keycloak - authority is
https://<host>/realms/<realm>.
Reverse proxy (Caddy/Traefik/nginx)
Make the public origin match Oidc__RedirectUri and Cors__AllowedOrigins__0, trust X-Forwarded-*, and serve over HTTPS.
krint.example.com { reverse_proxy krint:8080 }Oidc__RedirectUri=https://krint.example.com/
Oidc__PostLogoutRedirectUri=https://krint.example.com/
Oidc__RequireHttpsMetadata=true
Cors__AllowedOrigins__0=https://krint.example.comWith the bundled Keycloak, also set KC_HOSTNAME=https://sso.example.com and the matching Oidc__Authority. Oidc__InternalAuthority stays the in-cluster URL.
Operations
Upgrade
docker compose pull krint && docker compose up -d krintMigrations run on startup; the vault, metadata, and provisioned containers are preserved. Pin a version by replacing :latest with a published tag.
Back up the ./db/ (or postgres-data volume) and ./backups/ directories before major upgrades. Provisioned-instance volumes are independent and survive docker compose down.
Schedule dumps from the Backups page - they land in ./backups/.
Provisioned containers are labelled as a Compose project named krint-databases, so Docker Desktop (and docker compose ls) groups them into their own cluster - separate from KRINT's own stack - instead of listing them as loose containers.
Troubleshooting
Common errors & fixes
| Symptom | Fix |
|---|---|
Vault:MasterKey must decode to 32 bytes | Regenerate with openssl rand -base64 32. |
401 invalid_token: issuer is invalid | Oidc__Authority must match the IdP's issuer byte-for-byte. |
401: signature key was not found | API can't reach JWKS - check Oidc__InternalAuthority / KC_HOSTNAME_BACKCHANNEL_DYNAMIC. |
| "You're not allowed to access this service" | IdP authenticated the user but the client policy denies them - allow the user/group. |
CORS error on /api/* | Cors__AllowedOrigins__0 must match the SPA origin (no trailing slash). |
Cannot connect to the Docker daemon | The /var/run/docker.sock bind is missing from the krint service. |
<engine> container did not become ready on provision | KRINT reaches provisioned DBs over its own Docker network (the default compose network) and falls back to the host port - keep extra_hosts: ["host.docker.internal:host-gateway"] on the krint service for that fallback. |
SocketException (13): Permission denied | Running as non-root without socket access - keep the default root user or group_add the docker GID. |
No free host port in range | krint.yaml port_ranges exhausted - delete an instance or widen the range. |
| DB auth fails after changing the password | POSTGRES_PASSWORD only applies on first init - reset in-DB or wipe the volume. |
Your app gets Name or service not known or Connection refused for a provisioned DB | The host shown for an instance is only an address on the machine running the container. Connect by container name on KRINT's Docker network. See Connect an app to a node-hosted database. |
permission denied for schema public on your app's migrations | Postgres 15+ dropped the implicit CREATE grant. Make your app's role the owner of its database. |
See also: Declarative instances · Nodes · Developer setup