Skip to content

Hosting dbt Charts

dbt Charts is a YAML syntax around SQL for building dashboards. You describe the queries and the charts in YAML, it runs the SQL against your warehouse, and it renders the result.

It suits Stackboard because of what it renders. --format html produces one standalone document with the whole board drawn as an SVG inside it, fonts embedded, no scripts loaded from anywhere. That is an app already.

dbt Charts uv tool install dbt-charts, or pip install dbt-charts. Python 3.10 to 3.13
A dbt adapter For your warehouse: BigQuery, Snowflake, Postgres, Redshift, Databricks, Athena, Spark, Trino, or DuckDB
A board A .yml file describing the queries and charts
A git repository Where the rendered HTML is committed, and what Stackboard reads

Rendering runs your board’s SQL, so the machine doing it needs warehouse credentials. Stackboard never does: it reads the finished HTML and nothing else.

Terminal window
dct render boards/revenue.yml --format html

That writes renders/revenue.html by default. Put it where the repository wants it instead with --output:

Terminal window
dct render boards/revenue.yml --format html --output dist/index.html

Board variables are resolved at render time, passed with --var:

Terminal window
dct render boards/revenue.yml --format html --var region=emea --var days=90

Stackboard runs no build, so the rendered file is what you commit. A repository holding both the source and the output keeps the board auditable next to the picture it produced:

boards/
revenue.yml the board, read by dbt Charts
dist/
index.html the render, read by Stackboard

Nothing else has to be committed. The render is self-contained, so there are no assets beside it to keep in step.

Add an app pointing at the repository, with the entry path set to the rendered file:

Field Value
Repository Your repository URL
Entry path dist/index.html

Stackboard pins the commit it read, so the board on screen is a specific render you can trace back to a specific commit, not whatever the branch happens to say today.

Charts Drawn as SVG, so they render with no JavaScript and no network
Tables Paginate. Each page is pre-rendered and a small inline script switches between them
The data A snapshot, taken when you rendered. The page does not query anything
Filters Not interactive. Filtering needs a host that can re-run the board’s SQL, and a static page has no way to

For more than one view of a board, render it more than once. A board rendered per region, committed as several files, becomes several apps or one app per entry path, each pinned and each shareable on its own.

The snapshot is only as fresh as the last render, so refresh it where your warehouse credentials already live. A scheduled job that renders and commits is enough, and Stackboard picks up the new commit:

.github/workflows/render.yml
name: Render board
on:
schedule:
- cron: "0 6 * * 1-5"
workflow_dispatch:
jobs:
render:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv tool install dbt-charts
- run: dct render boards/revenue.yml --format html --output dist/index.html
env:
# Whatever your adapter needs
DBT_PROFILES_DIR: .
- run: |
git config user.name "board renderer"
git config user.email "noreply@example.com"
git add dist/index.html
git diff --quiet --staged || git commit -m "chore: render revenue board"
git push

A render that produces the same bytes commits nothing, so a board whose numbers have not moved leaves no empty commits behind.

A board with a large table renders a large SVG. The entry document has a 20 MB budget, which is a lot of chart and not much raw table, so cap long tables in the board rather than finding the limit with a render.