# Grafana

> Run the Grafana observability dashboard as a managed container.

---

LLMS index: [llms.txt](/llms.txt)

---

`pg addon install grafana` runs [Grafana](https://grafana.com/oss/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

```bash
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:

```bash
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:

```bash
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

```yaml
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

```bash
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:

```bash
pg addon stop grafana
pg addon start grafana
```

## Auto-start on Boot

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

```bash
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](/docs/autostart/) for the full mechanism, status and disabling.

## Remove

```bash
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

```bash
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](/docs/addon/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.

---

Backlinks:

- [Addons](/docs/addon/)
- [Platform Support](/docs/platform/)
