Skip to content

Analyze the database directly

Connect psql or a GUI (pgAdmin, IntelliJ, DBeaver) to the self-host PostgreSQL. To browse ingested rows without SQL: lm data tables, then :tables in lm ui (CLI reference).

Three roles, three postures

The install creates three database roles:

Role Sees Can write Use it for
lumnik_readonly your ingested data only: schema connector, and ext for REST connectors. One tenant at a time no — refused by PostgreSQL itself day-to-day analysis of ingested data
lumnik_app one tenant at a time (RLS applies) yes reproducing the application's own posture
lumnik_admin every schema, every tenant (bypasses RLS) yes, including DDL cross-tenant checks, the core and identity schemas — read only, unless you know why

Passwords live in your .env (LUMNIK_READONLY_DB_PASSWORD, LUMNIK_APP_DB_PASSWORD, LUMNIK_ADMIN_DB_PASSWORD) — generated per install, never shipped.

Quick look, no tooling

The database publishes no port to your machine by default. From inside the container, psql connects without a password:

CID=$(docker compose -f docker-compose.selfhost.yml ps -q postgres)
docker exec -it $CID psql -U lumnik_readonly -d lumnik

Connect a GUI (pgAdmin, IntelliJ, DBeaver…)

Give the database a TCP door on your machine only, via a compose override — the shipped file stays untouched:

# analyse-ports.yml (next to docker-compose.selfhost.yml)
services:
  postgres:
    ports: ["127.0.0.1:5433:5432"]
docker compose -f docker-compose.selfhost.yml -f analyse-ports.yml up -d postgres

Then in your tool: host localhost, port 5433, database lumnik, one of the three roles above with its .env password. The 127.0.0.1: prefix matters — the database listens to your machine, not your network. To close the door, run the same command without the override file:

docker compose -f docker-compose.selfhost.yml up -d postgres

Opening or closing the door recreates the database container: the hub loses its database for a few seconds.

The one habit: name your tenant

Under lumnik_readonly or lumnik_app, row-level security filters every table by the current tenant — and until you name one, you see no rows at all. Start each session with:

SET app.current_tenant = '1';   -- your tenant id; a fresh install seeds tenant 1

In IntelliJ, put that line in the data source's startup script so every connection carries it. In pgAdmin, run it at the top of each Query Tool session.

Ingested data lands in schema connector as t_-prefixed tables, one per endpoint, each with the tenant_id column the isolation keys on. As lumnik_readonly:

lumnik=> select count(*) from connector.t_orders;
 count
-------
     0
(1 row)

lumnik=> SET app.current_tenant = '1';
SET
lumnik=> select count(*) from connector.t_orders;
 count
-------
     9
(1 row)

lumnik=> select count(*) from core.audit_event;
ERROR:  permission denied for table audit_event

\dn lists the schemas: connector, core, identity, platform, rag, public, and ext once a REST connector has run. Outside your ingested data, connect as lumnik_admin; it sees every tenant without a SET.