# Metabase recipe

URL: https://briq.cloud/cookbook/metabase

> Reports on a Postgres warehouse: load a dataset, build a dashboard, share a link to it.

Tags: app, analytics, stack

Guide:

- [Report on a dataset](https://briq.cloud/docs/guides/report-on-a-dataset.md): Load a public dataset into Postgres, chart it in Metabase, and share a link to the dashboard.

## Run it

MCP:

```ts
briq_run({
  recipe: "metabase",
  params: {
    "size": "small",
    "dbSize": "nano"
  },
  wait: true
})
```

REST:

```bash
curl -X POST https://api.briq.cloud/v1/briqs \
  -H "Authorization: Bearer $BRIQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipe": "metabase",
    "params": {
      "size": "small",
      "dbSize": "nano"
    },
    "wait": true
  }'
# → { "briqs": [{ "id": "bq_…", … }], "outputs": { "METABASE_URL": "…" }, … }

# ... when done, for each briq id
curl -X DELETE https://api.briq.cloud/v1/briqs/<id> \
  -H "Authorization: Bearer $BRIQ_API_KEY"
```

## Parameters

| Name | Type | Options | Default | What it does |
| --- | --- | --- | --- | --- |
| `size` | enum | small, medium | "small" | Metabase |
| `dbSize` | enum | nano, small, medium | "nano" | The warehouse: small or more for a dataset over a few million rows |

## Outputs

Returned by `briq_run` once the stack is healthy. Hosts resolve inside your team network; secrets are generated per run.

| Output | Value |
| --- | --- |
| `METABASE_URL` | `http://metabase.<stack>.internal:3000` |
| `METABASE_ADMIN_EMAIL` | `admin@metabase.local` |
| `METABASE_ADMIN_PASSWORD` | `<METABASE_ADMIN_PASSWORD>` |
| `DATABASE_URL` | `postgres://postgres:<WAREHOUSE_PASSWORD>@postgres.<stack>.internal:5432/warehouse` |
| `READONLY_DATABASE_URL` | `postgres://reporter:<REPORTER_PASSWORD>@postgres.<stack>.internal:5432/warehouse` |

## What it runs

| Briq | Image | Ports | Size | Volume |
| --- | --- | --- | --- | --- |
| postgres | `postgres:16-alpine` | 5432 | nano | 10 GB at /var/lib/postgresql/data |
| metabase | `metabase/metabase:v0.63.19.2` | 3000 | small | — |

## When to use it

* The agent has analysed a dataset and you would rather open charts than read a table in chat.
* You want a dashboard someone can open from a link, without an account.
* You want SQL questions over data the agent loaded, kept and rerun on fresh data.

Two briqs: `metabase` serves the dashboards, `postgres` is the warehouse it reads. On first boot
Metabase makes its admin account (password in `METABASE_ADMIN_PASSWORD`) and connects the
warehouse as `Warehouse`, read through a read-only role: nobody signed in to Metabase can change
the data. The full walk-through, from a CSV on the web to a shared link, is
[Report on a dataset](https://briq.cloud/docs/guides/report-on-a-dataset).

## Tips

* **Load from inside the warehouse**: the image has `wget`, so
  `wget -qO- <csv url> | psql -U postgres -d warehouse -c "\copy t from stdin with (format csv, header)"`
  in `briq_exec` never sends the data through the agent.
* **Tables the agent creates as `postgres` in `public`** are readable by Metabase. Another schema
  needs `GRANT USAGE ON SCHEMA s TO reporter; GRANT SELECT ON ALL TABLES IN SCHEMA s TO reporter`.
* **`mb` calls Metabase's API as the admin** from `briq_exec` on the `metabase` briq:
  `["mb", "GET", "/api/database"]`; `["mb", "POST", "/api/card", "-"]` with the JSON as `stdin`.
* **Native SQL questions** (`dataset_query.type: "native"`) are the shortest way to a chart. After
  loading a new table, `POST /api/database/<id>/sync_schema` makes it browsable in the UI too.
* **Share one dashboard**: `POST /api/dashboard/<id>/public_link` returns a `uuid`; the page is
  `/public/dashboard/<uuid>` on the briq's URL. Run with `expose: "private"` and keep the
  `?briq_token=` on the link you send, so the link is the only way in.
* **First boot takes about three minutes** while Metabase migrates its database: `briq_run` can
  answer while `metabase` is still `creating`; `briq_status` says when it runs. Later starts are
  faster, and the dashboards survive stops.
* **Our World in Data files**: aggregate rows (World, continents, income groups) have an empty
  `iso_code`, not `OWID_WRL`. Match the world on `country = 'World'`; `iso_code IS NOT NULL` keeps
  the countries.
