Deploy lumnik
Installation
Get the hub running: one command on a single host, or the Helm chart on Kubernetes.
Before anything: the prerequisites checklist.
Choose the path
- Self-host — one command, one machine. Start here.
- One HTTPS origin — add it when phones must reach the hub.
- Kubernetes / Helm — the cloud edition, for a cluster.
Self-host — one command
On a Windows PC?
Follow Deploy on Windows instead. It starts from zero, WSL2 and Docker Desktop included. The commands below are for Linux or macOS.
git clone https://github.com/icreated/lumnik-open.git && cd lumnik-open
./deploy/selfhost/up.sh --build
Which edition you get:
| You run | You get |
|---|---|
up.sh --build in a lumnik-open clone |
lumnik Open: ingestion, entities, views, processes, workflows, outputs, the PWA. No chat, no lm ask, no Ollama. |
up.sh without --build, with access to the published image |
lumnik Pro: Open plus chat and lm ask. Prepare Ollama first. |
The published image is private: the pull works only if your GitHub account was granted access.
Check what runs with lm module list (The two editions).
The stack you get:
flowchart LR
lmop["lm — the integrator"] --> hub
phone["a business user's phone"] -.->|"https, via the TLS proxy"| caddy
subgraph machine["one machine — docker compose"]
caddy["caddy — one https origin<br/>(optional TLS proxy)"]
hub["hub :8080<br/>API + PWA at /app/"]
kc["keycloak :8180<br/>realm lumnik"]
pg[("postgres<br/>internal")]
caddy -->|"/*"| hub
caddy -->|"/realms/*"| kc
hub --> pg
hub -.->|"OIDC"| kc
end
What the launcher does:
Generate secrets safely
It writes your passwords and keys into .env, at the root of the clone. Only you can read it. It never overwrites an existing .env. Back it up.
Build and start the stack
It builds the hub image (--build) or pulls it, starts hub + Postgres + Keycloak, and waits for the hub to be healthy. The first boot can take a minute.
Mint per-install passwords
It sets the hubadmin and integrator passwords in Keycloak and prints the URLs.
You know it worked when: the launcher ends like this:
==> hub is healthy
hubadmin: per-install password set (HUBADMIN_PASSWORD in .env)
integrator: per-install password set (INTEGRATOR_PASSWORD in .env)
Hub: http://localhost:8080 (health: /q/health)
Keycloak: http://localhost:8180/realms/lumnik
Next: point lm at the hub — server http://localhost:8080, OIDC http://localhost:8180/realms/lumnik
Stop the stack: deploy/selfhost/down.sh. Follow its logs: deploy/selfhost/logs.sh hub.
Re-run up.sh with the same flags at any time: your data and .env are kept.
The bundled Keycloak
- It serves one machine.
backup.shsaves it: Backup & restore. - The client's own directory (Keycloak, Okta, Entra ID): Bring your own IdP.
One HTTPS origin — the TLS proxy
A phone installs the PWA only from one https address. The proxy gives it one: the hub at /,
Keycloak under /realms/*. Layer it on after up.sh:
LUMNIK_TLS_HOST=erp.example.com \
docker compose -f docker-compose.selfhost.yml -f docker-compose.tls.yml up -d
- A public name gets a Let's Encrypt certificate by itself. Ports 80 and 443 must be reachable.
- A LAN IP or
localhostgets a local certificate, for testing. - The proxy listens on every interface, so phones on the LAN reach it.
LUMNIK_TLS_BIND=127.0.0.1restricts it. up.shkeeps the proxy: a later run re-applies the overlays the stack was started with.- Choose the address once. Changing it changes who signs the tokens: users created through the old address must be migrated (Keep the same public address).
Trust the local certificate on a phone
With a LAN IP, the phone warns and refuses to install the PWA. Copy the certificate out:
docker compose -f docker-compose.selfhost.yml -f docker-compose.tls.yml \
cp caddy:/data/caddy/pki/authorities/local/root.crt ./lumnik-local-ca.crt
Install lumnik-local-ca.crt on the phone. On iOS: AirDrop the file, then Settings → General
→ VPN & Device Management, then turn on full trust under Certificate Trust Settings.
Point lm at the hub
Install lm, then give it the server and the
OIDC issuer — nothing else:
lm config set-context selfhost \
--server http://localhost:8080 \
--oidc-issuer http://localhost:8180/realms/lumnik
lm config use-context selfhost
lm login # prints a URL + device code — open it, sign in as integrator
| User | Tenant | Password in .env |
|---|---|---|
integrator |
1 | INTEGRATOR_PASSWORD — change it before anything real |
hubadmin (superadmin) |
11 | HUBADMIN_PASSWORD |
--oidc-client-id defaults to lm-cli, the bundled realm's client. Set it only for your own IdP.
Check the login. On a fresh hub, the table is empty:
$ lm connector list
NAME TYPE TENANT SCHED WINDOW STATUS LAST RUN
How to…
Point the hub at an Ollama on another machine
Pro only. In .env, uncomment the OLLAMA_BASE_URL= line and set it to that machine, for
example http://10.0.0.5:11434. Then re-run up.sh with your flags. Left unset, the hub looks
for Ollama on the machine running Docker.
Switch the AI off
The chat, the ask, view generation and the embeddings stop. The data is kept. The PWA drops its
Chat tab. The value is exactly true or false: anything else stops the hub at startup.
| Install | Gesture |
|---|---|
| Self-host | LUMNIK_AI_ENABLED=false in .env, then re-run up.sh with the flags you installed with (--build, --search…) |
| Helm (cloud edition), first install | LUMNIK_AI_ENABLED=false ./deploy/helm/up.sh (chart value ai.enabled) |
| Helm (cloud edition), running cluster | helm upgrade <release> deploy/helm/lumnik --reuse-values --set-string ai.enabled=false, then kubectl rollout restart deployment/<release>-hub |
- Without
--build,up.shpulls the hub image. PinLUMNIK_VERSIONin.envfirst, or the switch is also an upgrade. - A plain
docker compose restartkeeps the old setting. - On a running cluster, the Helm
up.shrefuses to run: see Kubernetes / Helm.
Check it with lm module list: rag and llm read INACTIVE disabled:lumnik.ai.enabled.
What a switch back on restores: Switching the AI off.
Turn on email delivery
Until you set two lines, the hub only pretends to send mail. In .env:
LUMNIK_SMTP_HOST=smtp.example.com
LUMNIK_SMTP_MOCK=false
# optional: LUMNIK_SMTP_PORT=587 LUMNIK_SMTP_USERNAME=… LUMNIK_SMTP_PASSWORD=…
# LUMNIK_SMTP_FROM=lumnik@example.com LUMNIK_SMTP_STARTTLS=REQUIRED
Re-run up.sh with your flags. Then check the hub's log:
docker compose -f docker-compose.selfhost.yml logs hub | grep -E 'email delivery|LUMNIK_SMTP'
You want a line holding "message":"email delivery live via smtp.example.com". Otherwise:
| You set | Every start logs |
|---|---|
the host, not LUMNIK_SMTP_MOCK=false |
LUMNIK_SMTP_HOST is set but delivery is still mocked |
LUMNIK_SMTP_MOCK=false, no host |
LUMNIK_SMTP_MOCK=false but LUMNIK_SMTP_HOST is not set |
| neither | nothing: the hub stays mocked |
To switch email back off, comment the host line out. An empty LUMNIK_SMTP_HOST= stops the hub
at startup.
Rehearse without a mail provider. Start the bundled test mailbox, then point .env at it:
docker compose --profile mailpit -f docker-compose.selfhost.yml up -d mailpit
# .env: LUMNIK_SMTP_HOST=mailpit LUMNIK_SMTP_PORT=1025 LUMNIK_SMTP_MOCK=false
Re-run up.sh with your flags. Mail then shows at http://127.0.0.1:8025. What a failed send
does, and where to see it: Email notifications.
Offer Kotlin scripts to the integrators
A csv-file connector, or a REST, GraphQL or SOAP endpoint, can run a Kotlin script by naming
it: script: hooks:<name>.kts (Kotlin hooks). The scripts come
from you: copy the .kts files into deploy/selfhost/hooks/.
cp ean-check.kts deploy/selfhost/hooks/
| What | How |
|---|---|
| The hub sees the directory | read-only. Git ignores the files there. |
| A new or replaced script | taken up from the next batch of rows, without a restart |
| The integrator checks | lm hook scripts lists name, size and SHA-256. lm apply refuses a script that is missing or does not compile. |
| File names | lower-case letters, digits and hyphens, ending in .kts. Another name is not offered. |
A script runs with all of the hub's rights. Install only scripts you have read. On Helm, the scripts come from a ConfigMap.
Reach the hub from another machine
The hub (8080) and Keycloak (8180) listen on 127.0.0.1 only. Two ways in: an SSH
tunnel, or the TLS proxy — the one a phone needs to install the PWA.
Run against a managed or external PostgreSQL
Not on this path: self-host always runs its own Postgres. An external database is a job for the Kubernetes / Helm path, below.
Kubernetes / Helm
The cloud edition (commercial) ships a Kubernetes/Helm path — hub + Postgres + Keycloak with real OIDC, sealed secrets, and a managed-PostgreSQL mode. It is not part of this repository.