Host/instance configuration (domain, port, registry, global concurrency) lives in a .werkator.yml in the home directory of the user running the instance — one instance per OS user, matching the platform model; repo configuration stays in each repository's .git/werkator/. Repo-level instance keys get ignored with a warning once a home config exists. The open questions now name the decisions still needed: repo defaults in the home file, cwd-vs-registry precedence, repo naming, and whether the third .werkator.yml location should carry a distinct name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.1 KiB
Step 22: One Werkator Instance, Many Repositories
Prerequisites: none in code; step 21's Werkdock work is independent.
Read README.md first.
This step is a roadmap in sessions (A–E), like step 21; each session is sized for one focused Claude Code session.
The Problem
"One instance per repository" is a founding tenet (docs/Werkator-Konzept.md, AGENTS.md) — and on a Managed Webspace it does not scale even to two repositories.
Building Werkbaum next to Werkator on mih34 today means: a second pac user or a second service, a second assigned port, a second tunnel or domain, a second UI, a second metrics page.
Every repository added multiplies operations, while the instance-level resources (port, UI, watcher schedule, executor slots, metrics) could be shared.
The goal: one Werkator instance serves a set of repositories — one service, one port, one UI — while each repository keeps its own configuration, secrets, history, and artifacts.
The Guiding Idea: the Repository Stays Self-Contained
Everything repository-specific already lives inside the repository: the machine config with secrets in .git/werkator/, build results, auto-build slots, worktrees, buildenvs — and the artifact store is already keyed per repo path.
The multi-repo instance therefore does not absorb repository state; it becomes an aggregator over self-contained repositories.
Consequences:
- Adding or removing a repository is editing a registry entry, never a data migration.
- A repository can move between instances (or back to its own) without losing anything.
- Single-repo mode stays the degenerate case: a registry of one, implicitly the current working directory — existing installations keep working without any config change.
Key Ownership Splits
Today all config comes from the repo's own layers; multi-repo splits ownership:
- Instance-level (decided 2026-09-01: a
.werkator.ymlin the home directory of the user running the instance):server.*(port, bind address, public base URL / domain, nginx), the repository registry, the control token (one UI, one token),executor.maxConcurrentas the global cap, watcher interval, metrics. - Repo-level (unchanged, from the repo's own layers — machine config in its
.git/werkator/, committed project config, branch layer):gitea.*(each repo has its own owner/repo/token/statusContext),git.*credentials,builds, retention, per-repo watcher options (e.g.pullRequestGate), sandbox policy and its pinning. - Both: a per-repo concurrency cap below the global one may come later; not in the first cut.
The pinning model is untouched: pinned keys still come from each repo's machine config, and the branch layer still cannot reach them.
The Sessions
A — Decision and schema (ADR 0009)
- Write ADR 0009: revise the one-instance-per-repository tenet to one-instance-per-set; record the aggregator idea and the key ownership split above. Considered alternative to record: a federation dashboard proxying several single-repo instances — less invasive, but it keeps N services/ports and solves only the UI, not the operations burden.
- Define the instance config:
~/.werkator.yml(decided 2026-09-01) — the repository registry plus the instance-level keys above; one instance per OS user, which matches the platform model (pac users on a webspace, service users elsewhere).werkator serverwithout a home config serves the current directory exactly as today. - Decide the transition for instance keys that today sit in a repo's machine config (mih34's carries
server.*): once a home config exists, repo-level instance keys are ignored with a warning naming both files — never merged silently. - Define repo identity for display and routes: a short unique name per registry entry (default: directory basename), used as the route segment (
/repos/<name>/…) and UI grouping key. - Update
docs/Werkator-Konzept.mdand AGENTS.md wording ("one instance per repository set").
B — RepoContext refactor, behavior unchanged
- Introduce a
RepoContext(working dir, config loading, git access, result repository, artifact store key, watcher state) and thread it through executor, watcher, and server code paths that today implicitly use the singleworkingDir. - The executor becomes instance-global with repo-scoped pools: serialization per (repo, branch), the global
maxConcurrentacross repos;BuildResultneeds no schema change — results stay in each repo's own JSON file, the repo dimension exists only in memory and in routes. - Single-repo behavior, routes, and UI stay byte-identical; the full test suite is the acceptance gate.
C — The registry and N repositories
- Load the registry, build one
RepoContextper entry; fail the start loudly on duplicate names or unreadable repos (config-version violations abort only that repo's registration, like branch-config violations fail only that branch). - Watcher multiplexing: one poll cycle iterates the contexts (fetch, enqueue, prune per repo) with per-repo error isolation — one unreachable origin must not starve the others;
WatcherStategains the repo dimension for the health banner. - Startup recovery per repo; auto-build slots stay in each repo's
.git/werkator/. - CLI commands gain an optional repo selector and default to the current working directory, so
werkator statusinside a repo behaves as today.
D — Server, API, and UI scoping
- Routes gain the repo segment (
/api/repos/<name>/builds/…,/repos/<name>/builds/<key>); with exactly one registered repo the today-routes keep working (redirect or alias) so bookmarks and posted Gitea links survive. - Latest/branches/history views group by repo or gain a repo column; one instance-wide metrics page; one control token.
- Gitea status links use the repo-scoped URLs.
E — Rollout on mih34: Werkbaum joins
- Registry with the Werkator and Werkbaum repositories under the existing user, one service, one port, the existing tunnel.
- Write Werkbaum's
.werkator.yml: Gradle backend build and npm frontend build in the shared trimmed image (Node is already in it). - Record the deployment; retire the second-instance/second-user idea from the notes.
Open Questions
- May
~/.werkator.ymlalso carry repo defaults (e.g. onegit.account/git.tokenfor all repos of the same forge), merged beneath every repo's own layers — or is it strictly instance-only? Defaults are convenient but widen where secrets live; strictly-instance-only keeps the "repository stays self-contained" property clean. - What wins when
werkator serveris started inside a repository while a home config exists — the registry, the cwd, or is that an error demanding an explicit flag? - Repo names for routes and display: registry-entry name with directory-basename default — or always explicit?
- The same file name
.werkator.ymlnow exists in a third location (home, repo root committed, repo.git/werkator/) — accept the overload, or name the home file differently (e.g.~/.werkator-instance.yml)? - Fairness across repos when the global concurrency cap is contended (round-robin per repo vs. FIFO) — decide in session C with the real queue behavior at hand.
- Whether buildenv rootfs trees should be shared across repos (today each repo unpacks its own under
.git/werkator/buildenv/) — the natural answer is Werkdock's image store (step 21 session C), not instance-level state; until then duplicate unpacked rootfs trees are the accepted cost. - Whether
artifactKeyneeds a repo prefix or stays globally unique by construction (random suffix) — decide in session B when the routes are designed.
Acceptance Criteria
- Session A: ADR 0009 merged; registry and key-ownership documented in
docs/configuration.md. - Session B: full suite green with
RepoContextthreaded through; no route or behavior change observable. - Session C: an instance with two registered repos builds pushes in both, with per-repo error isolation proven by a test (one broken origin, the other keeps building).
- Session D: both repos browsable in one UI; single-repo installations keep their existing URLs.
- Session E: mih34 builds Werkator and Werkbaum from one service;
docs/deployment.mddescribes the registry setup.