How Docktree orchestrates Docker Compose across multiple git worktrees — and the tradeoffs you need to know before deploying it.
Without any docktree.yml or platform configuration, every worktree is fully isolated by default. When you run docktree up in a worktree, Docktree:
41000–49999).postgres in feature-a and your postgres in feature-b are completely separate containers, with separate data volumes.This is the right default for most cases, especially when worktrees have diverging database schemas (e.g. different migration states). No configuration is required to get this behavior.
Docktree sits between Git and Docker Compose. It reads your existing docker-compose.yml, generates an ephemeral override file for each worktree, and delegates to the Docker Compose engine. It never modifies your source files.
Git worktree Docktree Docker Engine
───────────── ──────────── ─────────────
feature-a/ ──up──▶ allocate ports ──▶ containers-a
feature-b/ ──up──▶ generate overrides ──▶ containers-b
rewrite env vars
manage port registry
The Platform Tier is an optional infrastructure optimization. Both the default mode and the platform tier give every worktree a fully isolated database — the difference is only in how that isolation is implemented, not whether it exists.
DATABASE_URL that points to its own data — nothing leaks between worktrees.The tradeoff is purely about resource efficiency, not isolation quality.
Use the platform tier when:
Stick with the default (separate containers per worktree) when:
Default (no platform tier): Platform Tier (shared infra): feature-a ──▶ postgres-a (own) feature-a ──▶ db_feature_a ─┐ feature-b ──▶ postgres-b (own) feature-b ──▶ db_feature_b ─┼──▶ shared postgres feature-c ──▶ postgres-c (own) feature-c ──▶ db_feature_c ─┘ Both: each worktree connects to its own isolated DATABASE_URL Difference: physical container vs. logical namespace in one server Memory: 3× database processes Memory: 1× database process Schema: each DB fully independent Schema: all DBs on one server version
Shared platform services are declared in the shared.services block of docktree.yml. Docktree natively supports the following service kinds:
| Service Kind | Supported Tenancy | Isolation Mechanism |
|---|---|---|
postgres | per_database, full_share | Unique database per worktree. Optionally cloned from a template database for instant seed data / migration state. |
mysql | per_database, full_share | Unique database name per worktree. |
mongodb | per_database, full_share | Unique Mongo database namespace per worktree. |
redis | per_database, full_share | Isolated Redis logical database indices (0, 1, 2 …) allocated per worktree. |
s3 | per_database, full_share | Unique bucket name per worktree (compatible with localstack / MinIO). |
generic | full_share | No partitioning — all worktrees share the same service instance. Use for stateless services or read-only dependencies. |
When using per_database tenancy, Docktree detects database connection environment variables declared in your Compose files (e.g. DATABASE_URL, DB_NAME, MONGO_URI) and rewrites them in the generated override so each worktree connects to its own isolated namespace automatically.
This works because Docktree reads the environment directly from your Compose YAML at startup — no runtime agent or sidecar is required.
Docktree rewrites environment variables it can see in your Compose files at startup. If your application retrieves secrets from Infisical, Doppler, Vault, AWS Secrets Manager, or any other runtime secret manager that constructs DATABASE_URL dynamically inside a shell command or entrypoint, Docktree cannot safely rewrite it.
In that case, use one of these approaches instead:
DATABASE_URL from the container environment first — have the wrapper respect an existing env var before fetching from the vault.Rule of thumb: If you can see the database URL as a static env var in
docker-compose.yml, Docktree can rewrite it. If it's assembled at runtime, it can't.
When using the platform tier with per_database tenancy, each worktree gets its own database but they all live inside the same server process. If a branch runs destructive migrations (dropping columns, renaming tables), those changes affect only that branch's database — but mistakes in schema tooling can sometimes touch the wrong database. Test destructive migrations in separate containers (the default mode) rather than the platform tier.
The platform stack is repo-scoped, not worktree-scoped. Run docktree platform up from the main repo root, not from inside a worktree. Running it from a worktree directory will likely fail to detect the correct shared services config.
Docktree is not supported on native Windows. On Windows, run it inside WSL2 with the Docker Desktop WSL integration enabled. All paths, port allocations, and Compose file detection work correctly under WSL2.
While Docktree isolates your development environments (giving each worktree its own ports, container names, networks, and volumes), it is not a security sandbox.
All containers still run on the same shared Docker daemon of the host machine. If a container has root access or mounts the Docker socket (/var/run/docker.sock), it retains the same security risks as standard Docker Compose. Docktree prevents operational collisions; it does not isolate system-level permissions or provide kernel-level security boundaries.
Docktree allocates ports from a registry scoped to the repository root. If you have two separate repositories that both use Docktree, their port ranges will not collide by default — each tracks its own registry in .docktree/. However, if you manually set ports.range to the same range in two different repos on the same machine, collisions are possible.