# Database Gateway — Cloudron Package [Database Gateway](https://github.com/kazhuravlev/database-gateway) (dbgw) is a web gateway for secure, controlled access to PostgreSQL databases: users log in via OIDC and run SQL through a unified web UI, with every query parsed and authorized by embedded **OPA policies** (per user, target, operation and table). Results are stored in a local PostgreSQL with shareable links for debugging and auditing. ## Packaging overview | Aspect | Choice | |--------|--------| | Pattern | Multi-stage Go build (JOURNAL pattern #2) | | Builder | `golang:1.26-alpine` (upstream's own build recipe, CGO on) | | Base image | `alpine:3.23` (musl runtime required — see below) | | Upstream | v0.24.0 (Go 1.26.1, Echo; frontend pre-built and embedded via `go:embed`) | | Addons | `localstorage` + `postgresql` (16) — storage for profiles, bookmarks, query results | | Auth | **Native OIDC (preferred)** wired to the Cloudron platform identity provider | | Runtime | `start.sh` waits for Postgres, seeds config + OPA policy, `migrate-up`, execs `run` | Why alpine instead of `cloudron/base`: the SQL parser is a cgo binding (libpg_query) — the binary must be built with `CGO_ENABLED=1` (a static build fails with `undefined: pg.Parse`) and therefore links musl, which does not exec on Ubuntu/glibc bases. Alpine runtime matches upstream's own image; bash/psql/jq/openssl are apk-installed for `start.sh`. Why not the official image: upstream publishes `ghcr.io/kazhuravlev/ database-gateway:latest` but tagging is `:latest`-only; building v0.24.0 from the cloned source pins the version and lets the ldflags carry the release tag into `/healthcheck`-style version output. ## Authentication (auth gate verdict: OIDC, preferred) Database Gateway authenticates **only** via OIDC — no local user database, no LDAP. This is the preferred row of the auth matrix: - `CLOUDRON_OIDC_ISSUER`, `CLOUDRON_OIDC_CLIENT_ID`, `CLOUDRON_OIDC_CLIENT_SECRET` (injected by Cloudron's platform OIDC provider, manifestVersion 2) are written into `/app/data/config.json` on first start. - Redirect URL: `${CLOUDRON_APP_ORIGIN}/auth/callback`. - Scopes: `openid profile email groups`; `role_claim` is `groups`, mapped `admins` → `admin`, `users` → `user` (Cloudron's built-in groups). - Access policy is then enforced per query by OPA (see below), not just at login. ## Ports | Port | Type | Purpose | |------|------|---------| | 8080 | `httpPort` | Web UI + LRPC API (only listener) | ## Configuration (persistent, in `/app/data`) - `config.json` — seeded once on first start (OIDC + storage from Cloudron env). **Add your database targets here** with the Cloudron file manager; restart the app after editing. See [upstream example](https://github.com/kazhuravlev/database-gateway/blob/master/example/config.json) for the full target format (connection, `default_schema`, `tables[].table` must be schema-qualified, `fields` allowlist). - `opa/simple.rego` — seeded default: Cloudron `admins` see all targets and may run any op; everyone else denied until an operator extends the policy (input subjects contain `user:` and `role:`). - `.cookie_secret` — random 32-byte hex, generated once; rotating it invalidates sessions. The **postgresql addon** database is the gateway's own storage (users, bookmarks, cached results) — not a query target. Targets point at the *other* PostgreSQL instances you want to reach. ## Build & test ```bash docker build --cgroup-parent ukrrs-batch.slice -t database-gateway-cloudron:test Package-Workspace/Infrastructure/database-gateway/ docker run --rm --entrypoint /usr/local/bin/database-gateway database-gateway-cloudron:test --version ``` ## Files - `Dockerfile` — multi-stage Go build (golang:1.26-alpine → alpine:3.23, musl) - `CloudronManifest.json` — manifestVersion 2, httpPort 8080, localstorage + postgresql addons - `start.sh` — DB wait, config/policy seeding, migrations, exec gateway - `.env.example` — environment knobs consumed at seed time - `logo.png` — from upstream `frontend/src/favicon-96x96.png`