Run many isolated dev environments on one machine, one per git worktree. Each worktree gets its own services, its own environment variables, and its own database cloned from a template. No Docker, no /etc/hosts, no port juggling, and it's built for the parallel AI
coding agents.
Why isola
Git worktrees let you check out several
branches at once. But the moment you run them, they collide.
Picture three things in flight: an AI agent refactoring on one branch, a spike on
another, a hotfix on main.
They all try to bind :3000 and :5432.
They migrate and seed the same dev database, so one branch's schema change
breaks another, and a destructive test on one wipes data the others still need.
You're juggling .env files, hunting down stray processes, and guessing which
server is which.
Every fresh worktree starts empty: dependencies to install, migrations to run,
code to generate, all by hand before it will even start.
isola gives each worktree its own running environment: processes, ports,
*.localhost URLs, injected env vars, and its own database (cloned from a
template, dropped with the worktree). It runs each service's setup step first, so
a fresh worktree installs, migrates, and comes up on its own. So N branches, or N
agents, run fully isolated, side by side, and clean up after themselves.
Features
Per-worktree isolation: every worktree runs as its own environment, with its own processes, ports, URLs, environment variables, and database
Isolated databases: every worktree gets its own database, created when it starts and removed when the worktree goes away
Automatic ports: each service gets a stable port, with no clashes between worktrees
Subdomain proxy: open each worktree in the browser at its own URL, over HTTP or HTTPS, with no hosts-file setup
Environment injection: each service is handed the port and URLs it needs to reach the others, so nothing is hardcoded
Automatic setup: each service can declare a setup step (install dependencies, run migrations, generate code) that isola runs before starting it, in the worktree's own environment, so a fresh worktree is ready with no manual prep. A top-level setup covers repo-root steps that belong to the whole worktree (a root install that wires up git hooks, generating a shared client), run once before any service
AI-agent friendly: machine-readable output plus an installable agent skill, so coding agents can drive it
TUI dashboard: a terminal dashboard to start, stop, and monitor everything at once
Zero dependencies: just one binary to install, no Docker
Per-worktree overrides: change commands, ports, or environment per branch
Demo
Quick Start
Set it up with your coding agent (recommended)
Run your agent in your project, paste this, and you're done:
Set up isola for this project.
1. Add the isola skills: `npx skills add cyucelen/isola`.
2. Use the `isola-init` skill to do the setup. If it isn't available in this session, follow its guide: https://github.com/cyucelen/isola/blob/main/skills/isola-init/references/setup.md
Next project?/isola-init in any new repo.
Or set it up manually
1. Install
# macOS (Homebrew)
brew install cyucelen/tap/isola
# Linux (.deb / .rpm are attached to each release):
# https://github.com/cyucelen/isola/releases/latest
sudo dpkg -i isola_*_linux_amd64.deb # Debian/Ubuntu
sudo rpm -i isola_*_linux_amd64.rpm # Fedora/RHEL
# Arch (AUR)
yay -S isola-bin
# Any platform with Go
go install github.com/cyucelen/isola@latest
# Or build from source
git clone https://github.com/cyucelen/isola.git
cd isola
make build
See the install guide for prerequisites and platform notes.
2. Initialize
cd your-project
isola init
# Creates .isola.toml in the repo root
3. Configure
Edit .isola.toml to match your project. Each service declares its own env;
values can reference isola-provided sources with ${...}:
# Top-level, above any [section]: a repo-root step run once per worktree on up,
# before any service (e.g. a root install that also sets up git hooks).
setup = "pnpm install"
[services.web]
command = "pnpm run dev"
setup = "pnpm install" # runs before command on each up (deps, migrations)
dir = "web"
port_range = { min = 3100, max = 3199 }
proxy_port = 3000
# Injected into the process, and written to the service's env file:
[services.web.env]
API_URL = "${services.api.url}"
[services.api]
command = "go run ./cmd/server"
dir = "api"
port_range = { min = 8100, max = 8199 }
proxy_port = 8000
[services.api.env]
DATABASE_URL = "${accessories.database.url}"
# A background worker: no port_range/proxy_port, so no $PORT and no URL.
# isola still runs, manages, and injects env into it.
[services.worker]
command = "pnpm run worker"
dir = "api"
# A per-worktree database, cloned from a seeded template:
[accessories.database]
kind = "postgres"
server_url = "postgres://postgres@localhost:5432/postgres"
clone_from = "myapp_dev"
name = "myapp_${ISOLA_BRANCH_SLUG}"
Reference a sibling's URL or port, an accessory's connection string, or a
built-in with ${...}, and isola resolves each to this worktree's real values.
Full walkthrough in the isola-init setup guide.
4. Start services
isola up # this worktree's services (also auto-starts the proxy)
isola up --all # every worktree's services
Your services are now reachable at
http://<branch-slug>.<project>.localhost:<proxy_port> (e.g.
http://main.myapp.localhost:3000), where <project> is this repo's name
(default: its directory). One machine-wide proxy serves every project and runs
until isola proxy stop; HTTPS and opting out live under
[proxy]. Manage everything interactively with isola dash.
If you set up by hand, install the agent skill too so your agents can drive isola
day to day: npx skills add cyucelen/isola.
Worktree setup automation
Each worktree's environment needs to be started (isola up) and, when the
worktree goes away, stopped. Do that by hand, or have isola do it automatically.
Manual
Run isola up in the worktree to start it, isola down to stop it:
git worktree add ../my-feature -b my-feature
cd ../my-feature && isola up
Automatic (git hook)
Install a post-checkout hook once and every new worktree comes up on its own:
isola hooks install # just this clone
isola hooks install --shared # share with your team
Anything that makes a worktree runs git worktree add underneath (your editor, a
parallel-agent tool like Orca or Herdr,
or plain git), so this one hook covers them all. It runs isola up on new
worktrees only, and does nothing without a .isola.toml.
Skip once:ISOLA_NO_UP=1 git worktree add ...
Using Orca?isola orca wires it into orca.yaml instead, and the git hook stands down so nothing runs twice.
Check or remove:isola hooks status, isola hooks uninstall.
When a worktree is removed
Git has no worktree-removal hook, so isola reconciles: a removed worktree is torn
down automatically, its services stopped and the per-worktree databases it
provisioned dropped, on the next isola up and by the shared proxy in the
background. It only ever drops resources isola created. isola down --prune runs
the same teardown on demand, and isola destroy tears down the current worktree.
Environment Variables
Each service declares its own env. Use ${...} references and isola fills in
this worktree's real values, so nothing is hardcoded per branch. On a branch
feat-login:
Reference other services, accessories, and built-ins; isola injects the values
into the process. It also writes them into the service's .env file, updating an
existing file rather than creating one, so tools that read .env directly get the same isolated values. Full
list in the Configuration Reference.
Accessories
An accessory is an isolated stateful dependency isola creates per worktree,
brought up on isola up and dropped on isola down --prune. isola connects to
your existing server (it never manages the server itself) and exposes a
connection string that a service references explicitly. Two kinds ship today:
Kind
Each worktree gets
postgres
Its own database, physically cloned from a seeded template (via pgx, no psql needed)
redis
Its own numbered logical DB, allocated collision-free and flushed on reset/drop
A service opts into an accessory by referencing its URL under whatever env key it
wants; the same value goes to the process env and the service's env file:
Everything lives in a single .isola.toml at the root of your repository:
services (with their env), per-worktree accessories, the reverse proxy, and file
handling. The Configuration Reference documents
every block and field; the Quick Start above covers the common
case.
Commands
Command
Description
isola init
Create a .isola.toml configuration file
isola up
Start services for the current worktree
isola up --all
Start services for all worktrees
isola up --service
Start a specific service only
isola down
Stop services for the current worktree
isola down --all
Stop services for all worktrees
isola down --prune
Stop services, then drop deleted worktrees' state and databases
isola destroy
Stop the current worktree's services and drop its database (opposite of up)
isola ls
List all worktrees, services, ports, status, and URLs
isola logs [worktree]
Tail a worktree's service logs
isola accessory
Manage per-worktree databases: ls, up, reset, drop
isola dash
Open the interactive TUI dashboard
isola proxy start
Start the machine-wide reverse proxy (auto-started by up)
isola proxy stop
Stop the reverse proxy
isola trust
Install the CA certificate into the system trust store
isola hooks install
Install a git hook that runs isola up when a worktree is created (--shared to commit it)
isola orca
Add isola up to Orca's orca.yaml worktree setup hook
isola doctor
Run diagnostic checks on config and ports
isola version
Print version information
HTTPS is enabled per project with [proxy] https = true in .isola.toml (not a flag).
How It Works
project "myapp" project "api"
┌───────────────────────┐ ┌───────────────────────┐
│ main frontend :3100 │ │ main web :3140 │
│ feat frontend :3117 │ │ dev web :3151 │
└───────────┬───────────┘ └───────────┬───────────┘
│ (each project registers in ~/.isola/registry)
└──────────────┬───────────────────┘
┌──────────────▼──────────────┐
│ isola proxy (machine-wide) │
│ binds :3000, :8000, ... │
│ routes by <branch>.<proj> │
└──────────────┬──────────────┘
▼
main.myapp.localhost:3000 main.api.localhost:3000
feat.myapp.localhost:3000 dev.api.localhost:3000
Port allocation: each service gets a backend port via FNV32(branch:service) % range, skipping ports already in use so projects never share one. Stable across restarts.
Process management: services run as child processes with process groups. Logs go to .isola/logs/.
Shared proxy: one machine-wide daemon reads ~/.isola/registry, binds the union of every project's proxy_ports, and routes each request by its <branch>.<project>.localhost host to that project's backend (resolved live from the project's own state).
**.localhost**: per RFC 6761, modern browsers resolve *.localhost (including <branch>.<project>.localhost) to 127.0.0.1 automatically.
TUI Dashboard
Launch it with isola dash:
Shell Completion
isola supports shell completion for bash, zsh, fish, and PowerShell.
bash:
source <(isola completion bash)
# Or for persistent use:
isola completion bash > /etc/bash_completion.d/isola
zsh:
isola completion zsh > "${fpath[1]}/_isola"
# You may need to start a new shell for this to take effect.
fish:
isola completion fish | source
# Or for persistent use:
isola completion fish > ~/.config/fish/completions/isola.fish
PowerShell:
isola completion powershell | Out-String | Invoke-Expression
# Or for persistent use:
isola completion powershell > isola.ps1
# and add ". isola.ps1" to your PowerShell profile.
Troubleshooting
Start with isola doctor, which checks your config and ports. The
Troubleshooting guide covers the common failures and
their fixes: services that won't start, port conflicts, stale processes, proxy
routing, and HTTPS.
Yes. isola up starts your services on their allocated ports; reach them
directly at localhost:<port>. The proxy (and its *.localhost routing) is
optional; disable auto-start with [proxy] enabled = false.
Where does isola keep its files?
Two places. Per repository, in .isola/ at the main worktree root: runtime
state (service PIDs, port assignments, accessory handles) in .isola/state.json
(file-locked for concurrent access), and service logs at
.isola/logs/<branch-slug>.<service>.log.
Machine-wide, in ~/.isola/: the shared proxy's registry (registry.json)
and daemon state (proxy.json), the proxy log (logs/proxy.log), and the
auto-generated HTTPS CA and certificates (certs/), so one proxy and one trusted
CA cover every project.
Contributing
Fork the repository
Create your feature branch (git checkout -b feature/amazing)
Commit your changes (git commit -m 'feat: add amazing feature')
Push to the branch (git push origin feature/amazing)
Open a Pull Request
# Development
make build # Build binary
make test # Run tests with race detector
make lint # Run golangci-lint
make all # fmt + vet + lint + test + build
Credits & prior art
isola is a fork of portree by fairy-pitta, and builds directly on its worktree-aware process management, hash-based port allocation, and *.localhost reverse proxy. Huge thanks to the portree authors for that foundation.
If you just need per-worktree dev servers (port allocation and subdomain routing, without databases or other stateful isolation), portree is the simpler, focused tool. isola extends that model toward full per-worktree environment isolation: databases and other stateful accessories, cloned from a template and torn down together with the worktree.
Package postgres implements the "postgres" accessory kind: a per-worktree database cloned from a seeded template on a shared, user-managed Postgres server.
Package postgres implements the "postgres" accessory kind: a per-worktree database cloned from a seeded template on a shared, user-managed Postgres server.
Package expand provides ${VAR} interpolation shared between service env building and accessory drivers, so substitution behaves identically everywhere.
Package expand provides ${VAR} interpolation shared between service env building and accessory drivers, so substitution behaves identically everywhere.