| title | Develop Druks |
|---|---|
| description | Set up the repository, understand its architecture, change the database, and run verification. |
| sidebarTitle | Development |
| icon | code |
This guide is for changes to Druks itself. The backend and Vite development server operate on the host. Compose supplies isolated Postgres and Redis.
From the repository root:
docker compose -f deploy/compose.dev.yaml up -d
uv sync --locked --dev
cp druks.toml.example druks.toml
cp .env.example .env
python3 -c 'import base64, os; print(base64.b64encode(os.urandom(32)).decode())'Paste the generated value into secrets.secrets_key in druks.toml, then
initialize the development database:
uv run druks init-dbSettings reads ./druks.toml from the current directory. The example uses
[identity].mode = "none". The loopback dashboard has no authentication and
exactly one operator account. Your first provider connection creates this
account.
To use header mode with the development server, set
identity.mode = "header" in druks.toml. Set identity.header in the same
file. Then send the header with a browser add-on or
curl -H 'X-Edge-Email: you@example.com'.
The dev Compose project creates two databases:
- Development:
druks_devfor the host-run server. - Tests:
druks_testfor pytest. The suite rebuilds this schema.
.env.example points the server at druks_dev. The suite reaches
druks_test and Redis index 15 through DRUKS_TEST_DATABASE_URL and
DRUKS_TEST_REDIS_URL. It does not use the server settings. Thus, the two
databases cannot be confused.
Start the backend:
uv run uvicorn druks.api.server:app --host 127.0.0.1 --port 8001In another terminal:
npm --prefix frontend ci
npm --prefix frontend run devVite proxies API traffic to the backend. The production backend image instead contains the built SPA and serves it from FastAPI.
| Path | Responsibility |
|---|---|
backend/druks/workflows.py |
Public workflow, step, gate, scheduling, and start API |
backend/druks/agents.py |
Public agent descriptor and output contract |
backend/druks/durable/ |
DBOS integration, run projection, lifecycle internals |
backend/druks/apps/ |
Entry-point loading, discovery, author settings |
backend/druks/ui/ |
Page declarations, the block/value/field catalog, the page API |
backend/druks/events/, signals.py |
Event log, feed, and reactions |
backend/druks/webhooks/ |
Authenticated delivery framework and deduplication |
backend/druks/harnesses/ |
Harness invocation, authentication, usage, and capability manifests |
backend/druks/sandbox/ |
Drukbox lifecycle, SSH execution, workspace delivery |
backend/druks/api/ |
FastAPI composition and platform routes |
backend/druks/{mcp,skills,notifications,user_settings}/ |
Shared operator services |
backend/druks/contrib/software_factory/ |
Bundled reference app, not framework core |
frontend/src/ |
Shared dashboard shell and bundled app UI |
frontend/src/druksui/ |
The renderer for an app's Python pages |
backend/migrations/ |
Core/bundled schema history |
deploy/, scripts/ |
Images, Compose, Caddy, setup, and deployment |
The API process embeds DBOS and executes workflows. App modules register capabilities during boot, after DBOS initialization and before launch.
The main package registers bundled apps through pyproject.toml. CI also
installs backend/tests/druks-field_notes as a real editable distribution and
runs the proof-app tests. Those tests are the executable contract for:
- Headless and boot-time entry-point loading
- Role-module discovery
- Route and subject read-side mounting
- Independent migrations and table-prefix enforcement
- Workflow start, settings, and feed formatting.
If you change the author API, update the scaffold, proof app, author guide, and tests together.
Core and bundled historical tables use the core Alembic history:
uv run alembic -c backend/alembic.ini revision --autogenerate -m "describe change"
uv run druks init-dbFor an independently packaged app:
uv run druks makemigrations <app-name> -m "describe change"
uv run druks init-dbThe app owns its migration directory and version table. Review every autogenerated revision before applying it.
An encrypted column seals each value with <table>.<column> as the AAD. A
migration that renames that table or column must also decrypt every row under
the old name and re-encrypt it under the new one, or every stored value stops
decrypting. e3a9c7d1b5f4_providers_own_logins.py shows the step.
Backend checks:
uv pip install -e backend/tests/druks-field_notes # once per environment
uv run ruff check backend
uv run ruff format --check backend
uv run pytest backend/The suite builds its subjects from field_notes, the proof app. This standalone
distribution depends on Druks. It installs like an author app, not as a Druks
dependency. Install it one time for the full suite.
The pull-request backend workflow does the same. Pyright is available for local and editor use. It is not a CI gate.
Frontend checks:
npm --prefix frontend run lint
npm --prefix frontend test
npm --prefix frontend run buildThe frontend CI workflow runs those three commands on Node 22.
For documentation-only changes, also run these commands:
git diff --check
cd docs
mint validate
mint broken-links --check-anchors --check-redirectsMintlify builds docs/ directly. Its GitHub App owns deployments and
pull-request previews. The repository does not require a documentation-specific
GitHub Actions workflow.
Backend tests mock most provider boundaries. For a real local sandbox, run
Drukbox on the host from its own checkout
(DOCKER_SSH_USERNAME=druks make dev in
czpython/drukbox) and set:
[sandbox]
service_url = "http://127.0.0.1:8000"
service_token = "dev-token"
image = "ghcr.io/czpython/druks/sandbox:latest"uv run druks doctor --sandbox creates a real host. If you require a real
sandbox test, run this command. It is not part of the normal test suite.
An app's screens are Python. It declares them in pages.py, and the shell
renders them through frontend/src/druksui/. An installed wheel therefore
adds pages without touching the JavaScript bundle. The
Druks UI contract is the one description of what those pages
carry; change it first, then the renderer.
Two escape hatches remain, and both are for an app that needs full control of
its interface. An app can ship a standalone static frontend in its package's
dist/, served at /app/<name>. React code that joins the bundled dashboard
shell must already be in the SPA and register through
frontend/src/apps/index.ts; a wheel cannot put routes into that existing
JavaScript bundle.
See the frontend guide before adding dashboard pages.