Contributing¶
Thanks for your interest in dbt UI! Questions, ideas, bug reports and pull requests are all welcome.
How to contribute¶
The process is described in CONTRIBUTING.md. In short:
- Search Discussions and Issues first.
- Start a discussion for a new idea or a suspected bug.
- Once it's agreed, an issue is opened from the issue template.
- Send a pull request that links to the issue, with small commits that are easy to review.
Architecture¶
browser ──► frontend: nginx ──/api/──► backend: FastAPI ──► git, dbt (subprocesses)
React + TypeScript routes/, scheduler/
Frontend (frontend/): a React + TypeScript single-page app built with Vite. It uses Monaco for the code editor and React Flow with dagre for the lineage graph. In Docker, nginx serves the built app and forwards /api/ to the backend (docker/nginx.conf).
Backend (backend/): a FastAPI app (main.py) with one router per area in routes/: files, git, dbt commands, Python environments, env vars, MetaDV and the scheduler. It works directly on repositories on the server's disk:
- each user works on their own branch in their own git worktree of a repository, created next to it in a
worktreesfolder; - each worktree gets its own Python environment (
.dbt-ui-venv, built with uv) with dbt-core and the project'srequirements.txt; - dbt runs as a subprocess with
--project-dirand--profiles-dirboth set to the project folder.
Scheduler (backend/scheduler/): runs in-process in the backend, started from FastAPI's lifespan when DBT_UI__SCHEDULER_ENABLED=true. A cron loop queues due runs; a run manager executes them as dbt subprocesses with a concurrency limit and safeguards (executor.py). Each job has its own checkout of the default branch. Run data lives in SQLite or Postgres through SQLAlchemy, with Alembic migrations in scheduler/migrations/ applied on startup. When the scheduler is ready, manual runs from the editor go through it too (delegate.py).
Run it locally¶
You can run everything with Docker (see Install with Docker) and rebuild after changes with docker compose up --build. For a faster loop, run the two parts directly:
Backend (Python 3.11 or newer, uv, git):
Repositories are cloned to ~/git-repos (set GIT_REPOS_PATH to change it). The scheduler is off unless you set DBT_UI__SCHEDULER_ENABLED=true and DBT_UI__SCHEDULER_ENCRYPTION_KEY; its data then goes to ~/.dbt-ui. Some dbt safeguards (out-of-memory priority, memory cap) only apply on Linux. See the configuration reference for all variables.
Frontend (Node.js):
Open http://localhost:5173. The dev server calls the backend at http://localhost:8000 (VITE_API_URL).
More details are in backend/README.md and frontend/README.md in the repository.
Tests and checks¶
Backend tests use pytest and live in backend/tests. The scheduler tests use a stub dbt executable (tests/stub_dbt.py), so they need no warehouse or network:
cd backend
uv run pytest tests
# Also against Postgres (tables are created in the given database)
DBT_UI_TEST_PG_URL=postgresql://user:password@localhost:5432/dbtui uv run pytest tests
The frontend has no automated tests yet; check types and lint before sending a change:
Documentation¶
This site is built with Zensical from docs/ and mkdocs.yml. To preview it:
Pull requests that touch the docs are checked with zensical build --strict, which fails on broken links.
Screenshots¶
The screenshots and the animated slideshow on the home page are generated from a demo dbt UI by screenshots/run.sh (Docker and Node.js needed). It starts a separate dbt UI on http://localhost:8180 with the jaffle_shop_duckdb project, demo jobs and a day of run history, takes the screenshots with Playwright, frames them in a browser window and saves them to docs/images/. Your own dbt UI and its data aren't touched.
screenshots/run.sh # everything
screenshots/run.sh shoot # retake the screenshots (demo still running)
screenshots/run.sh down # remove the demo
Run it after visible UI changes and commit the updated images. See screenshots/README.md for details.