Grafana
pg addon install grafana runs Grafana — the open-source observability dashboard — as a managed container from the official upstream image. Like pgAdmin it is a top-level addon: it is not attached to any one PostgreSQL instance and fronts none of them automatically — you add datasources and dashboards through the web UI. v1 ships the pure web UI: configuration is environment-only (the GF_* variables), with no datasource or dashboard provisioning mounted.
The web login password is generated on first install, printed once, and stored in pg.yaml; retrieve it later with pg addon password grafana. Unlike pgAdmin, Listen defaults to 0.0.0.0: a monitoring dashboard is meant to be viewed from other machines, and containers run on the host network. The exposure is guarded by the generated strong password and closed self-signup — pass --listen 127.0.0.1 for loopback-only access.
Platform: Linux is the tested platform. The design supports macOS (the image is multi-arch; the container runs under the podman machine VM on the
pgcli-netbridge with the port published), but it has not been validated there yet.
Parameters
| Flag | Default | Description |
|---|---|---|
--name |
grafana |
Instance name (map key) |
--image |
docker.io/grafana/grafana:13.2.4 |
Image override |
--port |
auto (base 3000) | Host web port; auto-allocated from grafana_start_port |
--listen |
0.0.0.0 |
Bind address — defaults to every interface; use 127.0.0.1 for loopback-only |
--admin-user |
admin |
Web login user (GF_SECURITY_ADMIN_USER) |
--password |
auto-generated | Web login password (GF_SECURITY_ADMIN_PASSWORD; generated when omitted, applied only on the first startup — see below) |
--data-dir |
auto | Data directory override (default under the base dir) |
--force |
false | Recreate even when running — to apply changed ports, credentials or listen address |
The data directory holds Grafana’s own sqlite meta DB (grafana.db): dashboards, datasources, users and settings live there. It survives pg addon remove (delete it with --clean-data), so reinstalling under the same name revives everything. The image’s entrypoint does not fix up ownership (it runs as a baked uid 472, group root, and only warns when the directory is unwritable), so pgcli chowns the data dir on the host before creating the container — via podman unshare chown 472:0 under rootless podman, or a direct recursive chown when pgcli runs as root on a rootful host.
Install
The command pulls the image if needed, creates the container, and prints the URL and login password:
Open the URL in a browser and sign in with the printed credentials. Add a PostgreSQL datasource in the UI (Connect data → PostgreSQL; the host port of each managed instance is in its pg.yaml entry or shown by pg status <instance>). Build dashboards against it.
GF_SECURITY_ADMIN_USER / GF_SECURITY_ADMIN_PASSWORD take effect only while grafana.db does not exist — they initialise the login, they don’t overwrite it. Reinstalling over a kept data dir keeps the original web login; the printed password is then inert (install says so explicitly). To change the login of an existing store, run grafana-cli admin reset-admin-password <new> inside the container (pg exec-style: podman exec -it <container> ...), or remove with --clean-data and reinstall.
Re-running install reuses a running container and starts a stopped one — config changes only apply after a remove + reinstall or --force.
Listening and exposure
Grafana’s Listen defaults to 0.0.0.0: the container shares the host’s network namespace, so the dashboard is reachable on every interface at the assigned port. This is deliberate — a monitoring panel is viewed from other machines — and is protected by the generated strong admin password plus GF_USERS_ALLOW_SIGN_UP=false (self-service registration is off). For a loopback-only deployment pass --listen 127.0.0.1 (recreate with --force); pg addon list then shows http://127.0.0.1:<port>/. --listen <addr> accepts any specific interface address.
Retrieving the web login
The login is Grafana’s web sign-in, unrelated to any PostgreSQL role. Re-read the password at any time:
It is also stored in pg.yaml (addons.grafana.<name>.password).
Ports
Each Grafana instance owns a single port: the web UI. It is allocated from a pool with base grafana_start_port (default 3000 — Grafana’s conventional web port; it starts at 3000 because that is what bookmarks and docs expect), incrementing for every additional instance and skipping ports already taken by any managed container or the used_ports list. The allocation is written into pg.yaml, so a reinstall never re-hands-out a port another addon is using:
Grafana’s own ports share no number with any other addon’s pool bases; an explicit --port still takes precedence.
Configuration
Config is env-only (GF_*): the grafana.ini inside the image is not editable from the host. The variables pgcli sets are the login pair, the listener (port + bind address), analytics-off (GF_ANALYTICS_REPORTING_ENABLED / GF_ANALYTICS_CHECK_FOR_UPDATES), and GF_USERS_ALLOW_SIGN_UP=false. Anything beyond that is v1 out of scope; override the image with --image if you need a different build.
List
Passwords are hidden by default to keep the listing paste-safe; pg addon list --show-password reveals the plaintext value, and pg addon password grafana prints a single one bare for scripting.
Start and stop
Containers run with podman’s unless-stopped restart policy, so they come back automatically with the podman service after a host reboot. start/stop manage them explicitly:
Auto-start on Boot
To have a Grafana instance started automatically on host boot, enable it:
This flags the instance in pg.yaml and installs/refreshes a boot service (a systemd --user unit on Linux, a LaunchAgent on macOS) that runs pg start --autostart. A boot-time start only brings the container up — the data dir was already chowned at create time, so it needs no host privileges beyond podman itself. See Autostart for the full mechanism, status and disabling.
Remove
Under rootless podman, --clean-data deletes the data dir through podman unshare rm — Grafana writes as its container uid (472), which maps to a subordinate uid the host user cannot rm directly.
Logs
Troubleshooting
installfails with “chowning Grafana data dir … to 472:0” — the image runs as uid 472 and never fixes ownership itself, so pgcli chowns the host data dir before creating the container: throughpodman unshare chownunder rootless podman (where the mapped subordinate uid is the only correct target), or a direct recursivechownwhen pgcli itself runs as root on a rootful host (sudo podman, orpgrun as root). The manager shells out to the plainpodmanCLI, so this only fails outside those two cases — e.g. a non-rootpgexplicitly pointed at someone else’s rootful daemon. Run the install as root, or switch to rootless podman; a manualsudo chown -R 472:0 <data-dir>alone does not clear it, since pgcli re-checks ownership on every install attempt.- Login password doesn’t work —
GF_SECURITY_ADMIN_PASSWORDonly initialises the login whilegrafana.dbis absent. On an existing store usegrafana-cli admin reset-admin-password <new>inside the container, orpg addon remove grafana --clean-data+ reinstall (wipes the dashboards). - Dashboard unreachable from another machine —
Listendefaults to0.0.0.0, so the port is open on the host’s interfaces; a failure there is host firewall, not bind address. Checkpg addon listfor the port, then open it. - Port already allocated — the assigned port is held in
pg.yamland skipped for later instances; free it withpg addon removefirst, or pick another with--port N. - Data won’t load / the container exits with a permissions warning — the data dir must stay owned by uid 472; a manual
chownon the host dir or restoring a backup with the wrong ownership breaks it. Re-run install (--force) to re-apply the ownership fix.
Known limitations
- v1 is the bare web UI: no datasource or dashboard provisioning, no reverse-proxy subpath support (no
--root-urlflag —GF_SERVER_ROOT_URLis not set, so front it with nginx at the root path), and plaintext HTTP (TLS termination is the nginx addon’s job). - The host-side ownership chown is automatic under rootless podman (
podman unshare chown) and when pgcli runs as root on a rootful host (directchown). A rootful daemon talked to by a non-root pgcli (only via an explicitly configured remote socket) has no automatic path. - On macOS the container runs inside the podman machine; it has been validated on Linux only.