Environment isolation
Not containers — separate ports, databases, Redis namespaces, and env files for every worktree. Declare what your project needs once in .treeline.yml and a git hook installed by gtl install allocates resources on every checkout.
You run git worktree add and get a second checkout in seconds. Then you try to start it.
$ npm run dev
Error: listen EADDRINUSE :::3000
$ rails db:create
Database 'myapp_development' already exists
$ cat .env.local
PORT=3000
DATABASE_URL=postgres:///myapp_development
# Same as every other worktree.
Port collisions. Database conflicts. Shared env files. The checkout is fresh — everything around it is not.
One command changes everything
Creating worktree feature-auth
Allocated port 3010
Extra port 3011
Cloned database myapp_dev_feature_auth
Assigned Redis namespace myapp:feature-auth
Wrote .env.local
Copied config/master.key
Running setup commands…
Ready. cd ~/worktrees/feature-auth
The worktree has its own port, its own database (cloned with data), its own env file with interpolated values, and secrets copied from main. Boot immediately.
Isolation is the contract: ports, databases, env, and cross-repo URLs are deterministic per worktree. Once .treeline.yml describes your app, gtl new and gtl setup apply the same rules everywhere—no manual editing after every branch switch.
Main, a feature branch, and a PR review can run side by side with different ports and databases. You switch browser tabs—or editor windows with distinct title-bar colors—not terminal sessions, and you are not sharing one .env.local between checkouts.
New developers clone the repo and run gtl setup in a worktree. Database clone, env interpolation, commands.setup, and hooks run in a predictable order—no one-off “change these three env vars” instructions that drift from reality.
A worktree created for an agent or script gets the same allocation rules as yours: isolated DB, env, and supervisor-controlled start/stop. Teardown via gtl release frees resources when the branch is done—see AI Agents for MCP and JSON surfaces.
Everything is driven by .treeline.yml at the root of your project. The excerpt below is representative: ports and database, env seeding, commands.setup / commands.start, lifecycle hooks (the same four keys the CLI implements), and editor chrome.
project: myapp
port_count: 2
# Database
database:
adapter: postgresql
template: myapp_dev
# Environment
env_file: .env.local
copy_files:
- config/master.key
# Commands + hooks (order matches runtime)
commands:
setup:
- bin/setup
start: bin/dev
hooks:
pre_setup:
- bin/check-toolchain
post_setup:
- bin/warm-cache
pre_release:
- bin/confirm-safe-to-drop
post_release:
- bin/notify-teardown
# Editor
editor:
color: auto Every worktree gets a unique port automatically. port_count: 2 reserves sequential ports for multi-process apps (Vite + API, for example). Allocated ports are written to your env file via {port} tokens and available as $PORT in setup/start commands.
database.adapter picks the cloning strategy. PostgreSQL uses createdb --template for instant clones with seed data. SQLite is file-copied. Manage with gtl db reset, gtl db restore, or gtl db reset --from <source>.
Your env file is interpolated with allocated values — {port}, {database}, {redis_url}. Secrets like master.key are copied from your main worktree via copy_files.
editor.color: auto gives each worktree a deterministic title bar color so you always know which branch you're looking at. Works in VS Code, Cursor, Zed, and JetBrains. Override with a hex value or set editor.theme for full theme switching.
commands.setup runs after allocation, env write, and database clone. $PORT and interpolated values are available. commands.start is what gtl start and gtl review … --start execute via the supervisor.
hooks.pre_setup and hooks.post_setup wrap commands.setup; hooks.pre_release and hooks.post_release wrap freeing ports and dropping databases on gtl release. Pre-hooks abort the operation on non-zero exit; post-hooks warn and continue. Full ordering and examples: Hooks guide.
Each worktree gets a distinct color. Know which branch you're in at a glance.
Your web app and your API are different clones. Hardcoding localhost:3030 in the frontend env file breaks as soon as Git Treeline re-allocates the API. The registry already knows every project’s port—you need a way to reference it by name.
gtl resolve and {resolve:…} gtl resolve api prints the base URL for project api on the same branch name as your current worktree—no extra config when both repos track feature-auth. In .treeline.yml, API_URL: "{resolve:api}" resolves at setup time. Use {resolve:api/main} to pin a branch explicitly.
If the frontend branch does not match the API branch you need, gtl link api staging stores an override, regenerates the env file, and restarts the server automatically (visible in gtl status and gtl doctor). gtl unlink api clears it. Setup fails with a clear error if the target is not allocated.
$ gtl resolve api --json
# scripting / agents
env:
API_URL: "{resolve:api}"
One file, committed to your repo. Teammates without gtl installed are unaffected—the config file has no effect on builds or deployments.
gtl install Auto-detects your framework, creates .treeline.yml, allocates ports, and writes env — all in one step.