Shared services and namespaces
Isolation duplicates everything: two worktrees of a project with four postgres containers and a keycloak run eight postgres and two JVMs. A shared service runs once for the whole repository instead, and gives each worktree its own namespace in it: a database, a realm.
Declaring one
Section titled “Declaring one”[[job]]name = "postgres"kind = "service"cmd = "docker compose up -d postgres"stop = "docker compose stop postgres"scope = "shared" [job.ports] POSTGRES_PORT = 5432 [job.namespace] name = "app_{worktree}" # app_feat-x in worktree feat/x create = "scripts/db-worktree-add.sh" # run on every start: must be safe to run again remove = "scripts/db-worktree-drop.sh" # run by wtm clean and wtm prunewtm run init asks which compose services to share, then their namespace, row by row; wtm run job add|edit take the same fields as flags (--scope shared, --namespace-name, --namespace-create, --namespace-remove, --namespace-env KEY=VALUE).
Without a [job.namespace] the service is shared outright, data included: every worktree reads and writes the same data, which is foreign data to all of them.
Where it runs
Section titled “Where it runs”The real service runs in the main checkout, which never takes a port offset: a declared 5432 is the 5432 it binds, in every worktree. A worktree that starts it (run up of a profile holding it, or run start) starts it in the main checkout if it is not up yet, then holds it: run ps shows that hold as joined. The service stops only once no worktree holds it any more; run down in one worktree reports released for a service others still hold.
The namespace commands
Section titled “The namespace commands”wtm does not know what a database or a realm is: it runs your two commands at the right moment, with the right environment.
nameis data, never executed: wtm fills in{worktree}(the branch as a slug) and{ordinal}.app_{worktree}is the proposal.createruns on every start of the shared service, retried for a short while in case the service is not accepting connections yet. It must create the namespace if it is absent and do nothing if it is there.removeruns when the worktree is removed, never onrun stoporrun down. Leave it empty to keep the data.
Both are /bin/sh lines (or a script path) that read $WTM_NAMESPACE, $WTM_WORKTREE, $WTM_ORDINAL, the worktree's declared ports and URLs, and the extra variables of namespace.env. A create that clones main's database (CREATE DATABASE "$WTM_NAMESPACE" TEMPLATE app) starts each worktree from main's data without sharing it; see the developer notes for a complete script.
A worktree records each namespace it actually created in its meta.json (namespaces), the moment the service reports started. A worktree created and thrown away without ever starting the service owes nothing.
Telling the app: [[env]]
Section titled “Telling the app: [[env]]”A port link ([[env_port]]) says where the shared service answers: the same address for everyone. Which namespace a worktree holds is said by an [[env]] link, which writes a key's whole value from a template:
[[env]]file = "apps/api/.env"key = "DATABASE_URL"job = "postgres"value = "postgresql://app:app@localhost:{port.POSTGRES_PORT}/{namespace}"The placeholders are {namespace}, {port.NAME} (a port of that job, as it resolves in the worktree), {origin} (the job's published address), {worktree} and {ordinal}; anything else is refused when run.toml is read. A key may be written by an [[env]] link or an [[env_port]] link, never both. The links are settled when a worktree is created and whenever wtm env reconciles it, no daemon needed. The main checkout gets none: its databases and realms are its own, so its .env keeps the values it has, and wtm env main puts back the template's value of any key an earlier wtm wrote there (wt_main, acme-main).
What clean and prune do with the data
Section titled “What clean and prune do with the data”Removing a worktree runs in a fixed order: its jobs are stopped and checked gone, its on_clean hooks run, git removes the worktree, and only then is its data dropped. A failure before that last step leaves the data where it was.
- By default the namespaces are dropped: the confirmation names each one, and
--output jsonreports each asdropped,deferredorkept. --keep-datawithholds the drop.- A shared service that is down cannot drop anything. The interactive form asks whether to start it now or keep the data until wtm next starts it;
--yeskeeps it (deferred),--drop-datastarts the service, drops, and lets it go again. - A deferred drop is recorded in
pending-removals.tomland paid the next time wtm starts that service (run up,run start) or runsprune. A drop that takes longer than 30 s is deferred the same way. If a worktree of the same name is created again before then, the debt is withdrawn, never paid. - A namespace another live worktree reaches under the same name is never dropped (
kept).