Worktrees

Git worktrees of an application, each with its own address, dev processes and database.

Anvil serves each Git worktree of an application next to the application, with its own address, dev processes and database, so main and a feature branch can run at the same time.

Only an application's own folder (parked or linked) can have worktrees, not an alias (a linked name pointing at another application's folder).

What a worktree gets

For an application shop and the branch feature/login:

Foldershop--feature-login, next to the application's folder
Addressshop--feature-login.test. The branch is lowercased; anything other than letters, digits, . and - becomes -
Dev processesThe application's process settings, run separately in the worktree's folder with their own port. They don't start when Anvil starts
PHP, language version, HTTPSThe application's, unless set for the worktree
DatabaseIts own: shop_feature_login, empty or a copy of the application's. A name that could also belong to another application's worktree ends in a short hash
Redis and cache keysTheir own prefix, shop_feature_login_ (REDIS_PREFIX and CACHE_PREFIX for Laravel and Statamic)

Worktrees are listed under their application in the sidebar, including ones made with git worktree add.

Create a worktree

In the app: the application's Git tab › New Worktree…, or right-click the application › New Worktree…:

  • Branch, e.g. feature/login, with the resulting address below.
  • New branch, from the app's current one: on creates the branch; off needs an existing one.
  • Copy the database too: on copies the application's database; off starts empty for your migrations. Shown only when the database can be copied: a SQLite file, or MySQL or PostgreSQL in an Anvil service. On a server Anvil doesn't manage, the worktree points at a database named after it there, which you create yourself.

Create runs git worktree add, copies the files the hooks list and writes the worktree's values into its env files. A second sheet lists the hooks' commands, such as composer install, and runs them only on Run.

In the terminal, inside the application's folder (or with --app):

anvil worktree add feature/login --new
OptionMeaning
--app <app>The application, when you aren't in its folder
--newCreate the branch from the application's current one
--copy-dbStart the worktree's database as a copy of the application's
--copy-db=falseStart it empty
neitherFollow the hooks' Copy the database too (off unless you turned it on)

It then asks Run composer install; … in shop--feature-login? [y/N]; y creates the database, then runs the commands. Without a TTY it prints the commands and runs nothing. After N, the database is created the first time you run a command in the worktree through Anvil's terminal commands (or start it in the desktop app).

Its database

The database is created the first time you start the worktree, run something in it through Anvil's terminal commands, run its after-create commands, or click Prepare database on its Overview. Until then, for a database in an Anvil service, its address says the database hasn't been created and serves nothing. The Overview's Database line shows the name and whether it is a copy or empty.

If a database with the worktree's name exists and Anvil didn't create it, Anvil uses it only after Use existing on the Overview or anvil worktree use-db shop--feature-login. The worktree's migrations and tests then change that database.

A worktree never uses the application's database, unless you take the database keys out of the application's own hooks (see Where hooks come from). When something would point it there, the worktree is stopped and its Overview says why:

  • A database key the hooks set (DB_DATABASE, DB_URL, DATABASE_URL) is empty, or its placeholder has no value.
  • An env key named like a database (POSTGRES_DB, DB_NAME, DB_LEGACY_DATABASE) still has the application's value and the hooks don't set it. Give it a value in the hooks' keys.
  • composer.json sets extra.runtime.dotenv_overload (the env files would win over the worktree's settings) or extra.runtime.dotenv_path (Anvil can't read the env files it uses).

Tests

Anvil reads the first existing of phpunit.xml, phpunit.dist.xml and phpunit.xml.dist (ignoring commented-out lines), and .env.testing:

Test config namesTests run against
Another database (:memory: included)That one
The application's databaseThe worktree's own
The application's database, in a setting that wins over Anvil's (a <server> entry or <env force="true">, or a key Anvil doesn't set)Nothing: the worktree is stopped until you name a separate test database there

The message reads like:

Anvil can't point shop--feature-login at its own database (phpunit.dist.xml's
DB_DATABASE names shop's database and wins over Anvil's: name a test database
of its own there), so nothing of it runs

Worktree hooks

The hooks decide what a new worktree gets. Each application type has a preset; every field can be changed:

FieldWhat it doesLaravel preset
copyFiles or folders copied from the application (ignored files aren't in a checkout). A * in the last path segment matches any characters.env
copy_databaseCopy the database too: on, a copy of the application's; off, an empty oneunset (Settings › Worktrees decides)
keysEnv keys set for the worktree. Placeholders: {db} its database, {url} its address, {prefix} its key prefix, {port} its port, {name} its nameDB_DATABASE, DB_URL, DATABASE_URL = {db}; APP_URL = {url}; REDIS_PREFIX, CACHE_PREFIX = {prefix}
checkKeys that must not keep the application's value in the worktree's env filesDB_DATABASE, DB_URL, DATABASE_URL, REDIS_PREFIX, CACHE_PREFIX
after_createCommands offered once the worktree is made, run only on Run. {install} is the install command for the lock file's package manager, such as npm cicomposer install, {install}, php artisan migrate

For a key set to {db}, a URL value gets only its database name replaced, and a SQLite path maps to the file inside the worktree.

The keys go into everything Anvil starts in the worktree and into the env files Anvil copied: .env, .env.local, .env.development and .env.development.local; for Symfony, .env, .env.local, .env.<APP_ENV> and .env.<APP_ENV>.local. Anvil never writes .env.example, .env.testing or .env.test. If a checked key still has the application's value in an env file, the Overview names it and offers Rewrite with the hooks, which changes only those lines.

Where hooks come from

Each field comes from the first of these that sets it:

  1. The application's own hooks: its Settings tab, Worktrees section ([sites."<name>".worktree] in config.toml).
  2. The project's .anvil.toml, [worktree], shared with everyone who clones it.
  3. .worktreeinclude in the project: the files to copy.
  4. The preset for the application's type, edited in Settings › Worktrees (Reset to default restores Anvil's).
  5. Settings › Worktrees › Copy the database too.

Only the application's own hooks can take the database keys out (the worktree then shares the application's database). A .anvil.toml or preset that leaves them out still gets them from the next source.

anvil worktree hooks [app] prints the effective hooks, each field's source, and any mistakes.

# .anvil.toml
[worktree]
copy = [".env", "storage/app/public"]
after_create = ["composer install", "{install}", "php artisan migrate --seed"]

Step by step: a Laravel application

shop uses MySQL in an Anvil service, with DB_DATABASE=shop in its .env.

  1. In shop's Git tab, New Worktree…: feature/login, New branch on, Copy the database too on. Create makes ../shop--feature-login, copies .env and sets DB_DATABASE=shop_feature_login, APP_URL=http://shop--feature-login.test and REDIS_PREFIX/CACHE_PREFIX to shop_feature_login_.
  2. Run on the next sheet creates the database, then runs composer install, npm ci (or your lock file's install) and php artisan migrate.
  3. If your PHPUnit config forces shop (<server name="DB_DATABASE" value="shop"/>), the worktree is stopped until you name a test database there. Laravel's default phpunit.xml (SQLite :memory:) is fine.
  4. Start it from its Dev tab; it answers at http://shop--feature-login.test. Run tests with Run ▾ (the test script from composer.json or package.json) or php artisan test in its folder.

From the terminal, inside shop:

anvil worktree add feature/login --new --copy-db
# Run composer install; npm ci; php artisan migrate in shop--feature-login? [y/N] y

Then restart anvil start: it reads the application list at startup, so the worktree answers only after the restart. Its dev processes (Vite, the queue) don't run with the CLI alone: worktrees never start with Anvil, and anvil app start works through the desktop app. Run them yourself in the worktree's folder, e.g. npm run dev.

Remove a worktree

In the app: right-click the worktree › Remove Worktree…. Anvil stops its processes and removes its folder; the branch stays. Also delete … drops its databases too: the ones Anvil created, and one taken with Use existing. With uncommitted changes or unpushed commits the button becomes Remove anyway.

Before you confirm, Anvil lists ignored or untracked files that go with the folder: "These will be deleted with the folder: …". That covers the worktree's SQLite database, any *.sqlite, *.sqlite3 or *.db under database/, and .env files the application's folder has no copy of or whose values differ from its copy (apart from the keys the hooks write). vendor and node_modules aren't listed.

In the terminal:

anvil worktree remove shop--feature-login [--drop-db] [--force] [--yes]
Running processesRefuses while the worktree's dev server or any dev process runs. Stop them in the app, or with anvil app stop <worktree> while the app is open. With the CLI alone, stop what you started in the folder yourself; Anvil doesn't track it
--drop-dbDrops the databases recorded for it (created by Anvil, or taken with Use existing); without it they stay. A SQLite database in the folder is removed with it, as are ignored files such as .env, vendor and node_modules
--forceRemoves it even with uncommitted changes or unpushed commits
--yesWhen the folder holds files like those above, the command names them and asks first. Without a TTY (a script, an agent) it refuses until you add --yes

A database left behind by a removed worktree shows on the application's Overview with Delete and Keep.

Worktrees made outside Anvil

A worktree made with git worktree add is served with the same rules. Its Overview offers to copy the files the hooks list (such as .env) and install its packages. Whether its database starts as a copy follows Settings › Worktrees › Copy the database too, unless the application, its .anvil.toml or a preset sets it.

Commands

CommandWhat it does
anvil worktree list [app]Worktrees of one application, or of all: name, branch, folder
anvil worktree add <branch> [--app <app>] [--new] [--copy-db[=false]]Add a worktree, see Create a worktree
anvil worktree hooks [app]The hooks as they apply and where each field comes from
anvil worktree use-db <name> [--yes]Use a database with the worktree's name that Anvil didn't create; --yes doesn't ask
anvil worktree remove <name> [--drop-db] [--force] [--yes]Remove a worktree, see Remove a worktree

In config.toml

FieldMeaning
worktree_database"copy": Settings › Worktrees › Copy the database too is on. Left out when off
[worktree_presets.<preset>]Your changes to a preset (laravel, statamic, symfony, next, nuxt, node, python, other), with the hooks fields above
[worktrees."<worktree>"]Per worktree, written by Anvil: db ("copy" or "empty"), copies (the databases it uses, as service and name: made by Anvil or taken with Use existing) and adopted (set by Use existing)
[sites."<app>".worktree]The application's own hooks, with the hooks fields above

See Configuration.

On this page