Skip to main content

Adminer β€” the governed database cockpit

:::note Verified, live (2026-10-06) The operational counterpart to auto-generated schema diagrams: tbls documents the schema in Git; Adminer is the hands-on console to inspect, query and debug live data. Two different jobs β€” never conflate the design-doc with the operations console. :::

A single in-cluster Adminer instance for Day-2 database operations across both engines (PostgreSQL + MySQL/MariaDB). Deployed deliberately as a privileged utility, hardened on every axis.

What it is / isn't​

  • Is: a browser console to log into a database, browse tables, run read queries, analyse locks, trace a bug. Zero client install β€” an SSO'd bookmark.
  • Isn't: a design canvas, and not the way schema changes happen. Schema changes stay in migrations (Flyway/Alembic) β†’ GitOps; Adminer is read/inspect/operate.

How it's locked down​

ControlImplementation
IdentityAuthentik forward-auth, gated to the Platform Admins group β€” unauth β†’ 302 to SSO, non-members β†’ 403. Provisioned by minicloud-ops/scripts/authentik/forward-auth-provider.py.
ExposureInternal host adminer.10.0.0.200.nip.io only (Tailscale + minicloud CA) β€” no public/tunnel route for a DB console.
Workloadnon-root, readOnlyRootFilesystem, drop ALL caps, seccomp RuntimeDefault, resource limits, no service-account token.
Network (the key one)Scoped, not blanket. Adminer can only reach a DB whose namespace has an explicit allow-adminer-* ingress rule. Starter scope = the ai namespace (postgresql-ai β†’ 6 DBs: openwebui, litellm, ragdb, vaultwarden, flowise, mlflow). Verified: ai reachable, other DB namespaces (e.g. langfuse) blocked.
Least privilegeOperators should log in with a read-only DB role for inspection; schema/data mutations go through migrations.

Operate / verify​

# SSO gate (unauth must redirect, not serve):
curl -sI https://adminer.10.0.0.200.nip.io/ # β†’ HTTP 302 β†’ /outpost.goauthentik.io/start
kubectl -n adminer get pods # adminer 1/1 Running
# scoping: in-scope reachable, out-of-scope blocked (from the adminer pod):
kubectl -n adminer exec deploy/adminer -- php -r '$f=@fsockopen("postgresql-ai.ai.svc.cluster.local",5432,$e,$s,4);echo $f?"OPEN":"blocked";'

Log in (browser, after SSO): System = PostgreSQL or MySQL, Server = the in-cluster DNS (e.g. postgresql-ai.ai.svc.cluster.local, erpnext-mariadb-sts.erp.svc.cluster.local), plus the DB user/password/name.

Extend to another database (the one-rule pattern)​

Add an ingress allow in that DB's namespace (never a blanket cross-ns hole) β€” e.g. to reach langfuse:

# a NetworkPolicy in the langfuse namespace:
spec:
podSelector: { matchLabels: { cnpg.io/cluster: langfuse-postgres } }
policyTypes: [Ingress]
ingress:
- from: [{ namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: adminer } } }]
ports: [{ port: 5432, protocol: TCP }]

Manifests: minicloud-gitops/manifests/adminer/. Netpol starter: manifests/network-policies/ai.yaml (allow-adminer-postgresql).