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β
| Control | Implementation |
|---|---|
| Identity | Authentik 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. |
| Exposure | Internal host adminer.10.0.0.200.nip.io only (Tailscale + minicloud CA) β no public/tunnel route for a DB console. |
| Workload | non-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 privilege | Operators 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).