Skip to content

Grafana

Run the Grafana observability dashboard as a managed container.

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-net bridge 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

pg addon install grafana

The command pulls the image if needed, creates the container, and prints the URL and login password:

✓ Grafana installed: "grafana"
  Container:  pgcli-grafana-default-grafana
  URL:        http://0.0.0.0:3000/
  Login user:     admin
  Login password: <auto-generated>

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:

pg addon password grafana              # bare password on stdout
pg addon password grafana --file ./pw  # or write it to a 0600 file

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:

pg addon install grafana --name dash2   # second instance: next free port (3001 if taken)
pg addon install grafana --name dash3 --port 3100  # explicit port still wins

Grafana’s own ports share no number with any other addon’s pool bases; an explicit --port still takes precedence.

Configuration

addons:
  grafana:
    ui:
      name: ui
      image_tag: docker.io/grafana/grafana:13.2.4
      host_port: 3000
      listen: 0.0.0.0
      admin_user: admin
      password: <generated>
      autostart: false
      # data_dir: /srv/grafana-data    # optional, overrides the default location

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

pg addon list
Web add-ons (grafana):
  grafana (name: ui)
    Status:      running
    URL:         http://0.0.0.0:3000/
    Login user:  admin
    Data:        /home/you/.pgcli/addon/grafana/ui/data
    Image:       docker.io/grafana/grafana:13.2.4
    Container:   pgcli-grafana-ui

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:

pg addon stop grafana
pg addon start grafana

Auto-start on Boot

To have a Grafana instance started automatically on host boot, enable it:

pg autostart enable --grafana --name ui

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

pg addon remove grafana              # keeps the data dir — dashboards and login survive a reinstall
pg addon remove grafana --clean-data # deletes the data dir too (Grafana's data is gone)

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

pg logs addon grafana            # show recent console output
pg logs addon grafana -f         # follow
pg logs addon grafana -n 200     # last 200 lines (also -i / --tail)

Troubleshooting

  • install fails 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: through podman unshare chown under rootless podman (where the mapped subordinate uid is the only correct target), or a direct recursive chown when pgcli itself runs as root on a rootful host (sudo podman, or pg run as root). The manager shells out to the plain podman CLI, so this only fails outside those two cases — e.g. a non-root pg explicitly pointed at someone else’s rootful daemon. Run the install as root, or switch to rootless podman; a manual sudo 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_PASSWORD only initialises the login while grafana.db is absent. On an existing store use grafana-cli admin reset-admin-password <new> inside the container, or pg addon remove grafana --clean-data + reinstall (wipes the dashboards).
  • Dashboard unreachable from another machine — Listen defaults to 0.0.0.0, so the port is open on the host’s interfaces; a failure there is host firewall, not bind address. Check pg addon list for the port, then open it.
  • Port already allocated — the assigned port is held in pg.yaml and skipped for later instances; free it with pg addon remove first, 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 chown on 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-url flag — GF_SERVER_ROOT_URL is 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 (direct chown). 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.