anthonyiles / worktree-isolation
Per-worktree test database isolation and bootstrap automation for PHP projects.
Package info
github.com/anthonyiles/worktree-isolation
Language:Shell
pkg:composer/anthonyiles/worktree-isolation
Requires
- php: ^8.2
Requires (Dev)
- illuminate/support: ^11.0|^12.0
- orchestra/testbench: ^9.0|^10.0
Suggests
- illuminate/support: Required for artisan commands (worktree:install, worktree:clean). Not needed if using the standalone vendor/bin/ scripts.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-18 17:56:44 UTC
README
Per-worktree test database isolation and bootstrap automation for PHP projects.
Works with any PHP project and any development environment: native PHP (Herd, Valet), Docker Compose, Laravel Sail, or any standalone Docker image. No framework required — Laravel integration is included but optional.
The Problem
When using git worktree with a PHP project, each worktree needs:
- Composer and npm dependencies installed
- Environment files (
.env,.env.testing) configured - An isolated test database to avoid conflicts with other worktrees running in parallel
This package automates all of that. After installation, every git worktree add automatically bootstraps the new worktree — no manual steps required.
Requirements
- PHP 8.2+
- Git 2.54+ (for config-based hooks)
- MySQL (for per-worktree database isolation)
Installation
Every scenario starts the same way:
composer require anthonyiles/worktree-isolation --dev
This installs three commands under vendor/bin/ — worktree-install, worktree-setup, test, worktree-clean — kept in sync automatically by Composer. Nothing is copied into your project except the config file you choose to write (below). Laravel projects can swap vendor/bin/worktree-install for php artisan worktree:install in any scenario below — same flags, artisan just delegates to the same installer.
Then pick the section that matches your setup:
- Native PHP — Herd, Valet, or any local PHP/Node install
- Docker Compose — an app service defined in
docker-compose.yml - Laravel Sail — Sail, or any other standalone Docker image
Native PHP
For Herd, Valet, or any setup where composer, npm, and your test runner already run directly on the host.
vendor/bin/worktree-install
# or: php artisan worktree:install (Laravel projects)
This is the default runtime, so no --runtime flag is needed. It writes .worktree-isolation.env:
# Runtime driver: native | docker-compose | docker-image WORKTREE_RUNTIME=native # Test command (default: php artisan test) # WORKTREE_TEST_COMMAND=php vendor/bin/phpunit # Common settings WORKTREE_TESTING_ENV_FILE=.env.testing WORKTREE_TESTING_ENV_EXAMPLE=.env.testing.example WORKTREE_DB_PER_WORKTREE_KEY=TEST_DB_PER_WORKTREE
Non-Laravel projects should also set WORKTREE_TEST_COMMAND — see Custom Test Command.
Docker Compose
For projects where the app runs as a service in docker-compose.yml.
vendor/bin/worktree-install --runtime=docker-compose --compose-service=app
# or: php artisan worktree:install --runtime=docker-compose --compose-service=app
--compose-service should match the service name in your docker-compose.yml that has PHP, Composer, and Node available (default: app). This writes:
WORKTREE_RUNTIME=docker-compose # Test command (default: php artisan test) # WORKTREE_TEST_COMMAND=php vendor/bin/phpunit # docker-compose runtime settings WORKTREE_COMPOSE_SERVICE=app # WORKTREE_COMPOSE_FILE=docker-compose.yml WORKTREE_COMPOSE_PROJECT_BASE=my-project # Common settings WORKTREE_TESTING_ENV_FILE=.env.testing WORKTREE_TESTING_ENV_EXAMPLE=.env.testing.example WORKTREE_DB_PER_WORKTREE_KEY=TEST_DB_PER_WORKTREE
worktree-setup brings up its own Compose stack for each worktree — docker compose up -d — under an isolated -p <project> derived from WORKTREE_COMPOSE_PROJECT_BASE and the worktree's directory name (e.g. my-project-feature-auth), the same way the per-worktree test database name is derived. composer install, npm install, and vendor/bin/test all pass that same -p flag through to docker compose exec, so each worktree's stack — containers, networks, volumes — stays completely separate from every other worktree's. docker compose up -d before git worktree add is no longer something you need to do by hand.
WORKTREE_COMPOSE_PROJECT_BASE defaults to your project directory's name (sanitized: lowercased, non-alphanumeric characters collapsed to -); override it with --compose-project-base=NAME at install time if that would collide with an unrelated project on the same Docker host.
Not covered by this: fixed host port bindings in your docker-compose.yml (e.g. 8080:80) will still collide across worktrees — parameterize those yourself (e.g. ${APP_PORT:-8080}:80) if you plan to run multiple worktrees' stacks at once. vendor/bin/test assumes worktree-setup already brought the stack up for that worktree; if it hasn't, docker compose exec fails with Docker's normal error.
Laravel Sail
Sail is just Laravel's name for a pre-built Docker image, so it uses the docker-image runtime — this also covers any other standalone Docker image (non-Sail) the same way, just with different --docker-image/--docker-network values.
vendor/bin/worktree-install --runtime=docker-image --docker-image="sail-8.5/app" --docker-network="myproject_sail" # or: php artisan worktree:install --runtime=docker-image --docker-image="sail-8.5/app" --docker-network="myproject_sail"
--docker-image— the image Sail already built (check withdocker images, or seevendor/bin/sailconfig; typically<project>-<php-version>/app)--docker-network— the Docker network Sail's containers (including MySQL) run on, so the ephemeral test container can reach them (typically<project>_sail)
This writes:
WORKTREE_RUNTIME=docker-image # Test command (default: php artisan test) # WORKTREE_TEST_COMMAND=php vendor/bin/phpunit # docker-image runtime settings WORKTREE_DOCKER_IMAGE=sail-8.5/app WORKTREE_DOCKER_NETWORK=myproject_sail # WORKTREE_DOCKER_WORKDIR=/var/www/html # Common settings WORKTREE_TESTING_ENV_FILE=.env.testing WORKTREE_TESTING_ENV_EXAMPLE=.env.testing.example WORKTREE_DB_PER_WORKTREE_KEY=TEST_DB_PER_WORKTREE
composer install, npm install, and your test command each run via a throwaway docker run --rm against that image, attached to the given network — the image must already be built (vendor/bin/sail build, or docker compose build for a non-Sail standalone image).
Don't call vendor/bin/sail (or docker compose) directly from inside a worktree. Sail's own CLI checks whether its containers are already running and, if so, execs straight into them instead of starting fresh — and that container's bind mount is fixed to wherever it was originally started (normally your main checkout, since that's the stack WORKTREE_DOCKER_NETWORK points at). Run sail composer install or sail artisan test from a worktree and you'll silently install dependencies or run tests against the main checkout's files, not the worktree's — the exact bug this package exists to prevent, just reached via Sail's CLI instead of raw Docker Compose. vendor/bin/worktree-setup and vendor/bin/test sidestep this entirely: they never call sail, they run docker run --rm -v <this-worktree>:... directly against the built image, so the bind mount is always correct. Always use vendor/bin/test / vendor/bin/worktree-setup instead of Sail's CLI once you're working across worktrees.
Custom Test Command
By default, tests run via php artisan test. For non-Laravel projects (in any of the scenarios above), set a custom test command:
vendor/bin/worktree-install --test-command="php vendor/bin/phpunit"
Or set WORKTREE_TEST_COMMAND directly in .worktree-isolation.env:
WORKTREE_TEST_COMMAND=php vendor/bin/phpunit
For Other Engineers
After pulling a branch that has .worktree-isolation.env committed, each engineer just runs:
vendor/bin/worktree-install
# or: php artisan worktree:install (Laravel projects)
The command is idempotent — it detects the existing .worktree-isolation.env and only (re)configures the git hook.
Hook activation is local to that clone (git config --local), so each engineer runs this once per clone — same as any git-hooks tool (Husky, pre-commit, etc.), since git never auto-trusts hooks from a fresh clone. It is not tied to any branch: because the hook command is registered as an absolute path resolved at install time, worktrees created from any branch — including ones that never had this package's config committed — get bootstrapped automatically. You don't need to merge anything hook-related into every branch you plan to git worktree add from.
How It Works
Automatic Worktree Bootstrap
When you run git worktree add, the post-checkout hook detects the new worktree and runs worktree-setup (straight out of vendor/), which:
- Copies
.envfrom the main repo - Copies
.env.testing(or falls back to.env.testing.example) - Forces
TEST_DB_PER_WORKTREE=truein the worktree's.env.testing - For the
docker-composeruntime: brings up this worktree's own Compose stack (docker compose -p <isolated-project-name> up -d) - Runs
composer install(via the configured runtime) - Derives the per-worktree database name, creates it, and writes it as
DB_DATABASEin the worktree's.env.testing - Runs
npm install(via the configured runtime)
Per-Worktree Test Databases
The database name is derived from the worktree directory:
testing-{worktree-folder-name}
For example, a worktree at ../worktrees/my-project/feature-auth gets database testing-feature-auth. A safety guard ensures the derived name always contains "test" to prevent accidental use of production databases.
Because this name is written directly into .env.testing at bootstrap time (step 5 above), it applies no matter how you run tests — vendor/bin/test, sail test, php artisan test, vendor/bin/phpunit, or anything else that reads .env.testing the normal way. vendor/bin/test also re-derives and re-creates the database dynamically on every run, so it stays correct even if step 5 failed at setup time (e.g. the database wasn't reachable yet) or the worktree directory gets renamed later.
Running Tests
From any worktree:
vendor/bin/test # run all tests vendor/bin/test --filter=MyTest # filter tests vendor/bin/test tests/Feature/MyTest.php # specific file
Cleaning Up
Drop all per-worktree test databases:
vendor/bin/worktree-clean
# or: php artisan worktree:clean (Laravel projects)
This lists all databases matching the {base}-* pattern and asks for confirmation before dropping them. Use --force to skip the prompt.
Configuration
Runtime Drivers
| Driver | When to use | Requirements |
|---|---|---|
native (default) |
Herd, Valet, any local PHP/Node | PHP, Composer, Node on host |
docker-compose |
Docker Compose projects | Running docker compose up -d |
docker-image |
Sail or standalone Docker image | Pre-built Docker image |
.worktree-isolation.env (full reference)
Project-level configuration (committed to repo). The scenario sections above show the subset of these that matter for each runtime — this is the complete list:
# Runtime driver: native | docker-compose | docker-image WORKTREE_RUNTIME=native # Test command (default: php artisan test) # WORKTREE_TEST_COMMAND=php vendor/bin/phpunit # --- docker-compose driver --- # WORKTREE_COMPOSE_SERVICE=app # WORKTREE_COMPOSE_FILE=docker-compose.yml # WORKTREE_COMPOSE_PROJECT_BASE=my-project # --- docker-image driver --- # WORKTREE_DOCKER_IMAGE=myapp # WORKTREE_DOCKER_NETWORK=myapp_default # WORKTREE_DOCKER_WORKDIR=/var/www/html # --- Common --- WORKTREE_TESTING_ENV_FILE=.env.testing WORKTREE_TESTING_ENV_EXAMPLE=.env.testing.example WORKTREE_DB_PER_WORKTREE_KEY=TEST_DB_PER_WORKTREE # Additional env vars to forward to the test container (docker-image only) # WORKTREE_EXTRA_ENV_VARS=
Laravel Config (optional)
Laravel projects can also publish a config file:
php artisan vendor:publish --tag=worktree-isolation-config
This creates config/worktree-isolation.php which mirrors the .worktree-isolation.env settings through Laravel's config system.
Available Commands
All installed via Composer's bin mechanism — vendor/bin/* always matches the installed package version, nothing to republish on upgrade.
| Command | Purpose |
|---|---|
vendor/bin/test |
Run tests with per-worktree database isolation |
vendor/bin/worktree-setup |
Bootstrap a worktree (env files, dependencies) — normally run automatically by the git hook |
vendor/bin/worktree-install |
Install/configure worktree isolation (no framework needed) |
vendor/bin/worktree-clean |
Drop per-worktree test databases (no framework needed) |
AI Agent Integration
For the native runtime, the per-worktree database is baked into .env.testing at bootstrap time (see Per-Worktree Test Databases), so an agent running php artisan test directly still hits the correct, isolated database.
For docker-compose and docker-image (Sail) runtimes, that guarantee only holds if the agent goes through this package's commands. An agent that runs sail artisan test, sail composer install, or raw docker compose exec directly from a worktree can end up executing inside a container bind-mounted to a different checkout (see the warning in Laravel Sail above) — at that point it's reading the wrong worktree's .env.testing entirely, isolated database name or not. vendor/bin/test and vendor/bin/worktree-setup are the only commands that guarantee the correct worktree, in every runtime. Add this to your project's cursor rules or AGENTS.md:
**Worktrees:** If the working directory is a git worktree (`.git` is a file, not a directory), always use `vendor/bin/test` to run tests and `vendor/bin/worktree-setup` to install/update dependencies — never call `sail`, `docker compose`, `composer`, or `npm` directly. Those can silently execute inside another worktree's (or the main checkout's) container. `vendor/bin/test` handles runtime dispatch (native, Docker Compose, Sail) on top of the per-worktree database isolation already active in .env.testing.
License
MIT