Lanes and databases
A lane is an isolated copy of a site for one task: its own git worktree and branch, its submodules on their branches, a copy of vendor/ and node_modules/, a database forked from the site's, and its own port or host name. You can run the site and several lanes at the same time, and throw a lane away when the work is merged.
Why lanes
- An urgent fix while a feature is half done. Leave the feature branch as it is, open a lane on
main, fix, push, remove the lane. - Several AI agents at once. Each agent gets a lane, so they never edit the same files, run migrations on the same database or fight over a port.
- Reviewing a pull request with real data, next to your own work.
- A risky migration on a copy of the data instead of the real thing.
Plain git worktree gives you the code. A lane also gives you a database, an .env, a port, installed dependencies and initialised submodules, in about the time it takes to copy the database.
Create a lane
devkit lane new core-development crm-fix --start --progress
… git worktree add ~/Project/sites/core-development/.worktrees/crm-fix (branch feat/crm-fix)
… git submodule update --init --recursive
… copying vendor, node_modules
… forking database core_development to core_development_l1
… lane up core-development--crm-fix
lane core-development--crm-fix (index 1) on branch feat/crm-fix at ~/Project/sites/core-development/.worktrees/crm-fix
copied vendor (clonefile)
copied node_modules (clonefile)
database core_development_l1 forked from core_development (212 tables, 41250 ms)
url http://localhost:8107 (58310 ms)
core-development--crm-fix is up at http://localhost:8107 (PHP 8.3.36, db core_development_l1)
queue running
The lane is called core-development--crm-fix: site name, two dashes, lane name. Every command that takes a site takes a lane too.
| Part | What you get | Change it with |
|---|---|---|
| Name | lower-case letters, digits and single dashes, at most 32 characters | |
| Worktree | <site>/.worktrees/<name>; /.worktrees/ is added to the site's .git/info/exclude, so it never shows as untracked | |
| Branch | feat/<name>, created from the site's current commit when it does not exist, or checked out as it is when it does | --branch, --base <ref> (only for a new branch) |
| Submodules | initialised recursively, each put on its branch (the one the site's submodule is on, else the .gitmodules branch) when that branch holds the recorded commit; otherwise it stays detached, with a warning | |
| Copies | vendor/ and node_modules/, copy-on-write, plus the site's --copy-files | devkit site set <site> --copy-files … |
| Database | <db>_l<index>, a full copy of the site's database | --schema-only, --no-fork-db |
| Port, URL | artisan: its own port, http://localhost:<port>; caddy: https://<lane>.<site>.localhost | |
.env | the site's template rendered with the lane's database, port and URL | the site's template |
| Engine, PHP, stack | always the site's | on the site |
Fork with data, schema only, or empty
- Fork with data (default): every table with its rows. The copy runs on the database server itself, so nothing is dumped to disk; a large database takes a minute or two.
- Schema only (
--schema-only): every table, no rows. Use it when you will run migrations and seeders anyway. - Empty database (
--no-fork-db): no tables at all;lane upcreates the database. Use it formigrate:freshfrom scratch.
Forking needs the site's database to exist: start the site once (devkit site up <site> creates it), or use --no-fork-db.
In the app
New lane… in the site's … menu, the site detail or the Lanes section opens New lane from <site>: the Lane name, the Branch (Create feat/<name> from a base branch, or Use an existing branch), the Database (Fork with data, Schema only, Empty database) and Start the lane when it is ready (on). A summary shows the folder, database and URL before you press Create lane; then each step appears as devkit reports it.
If something fails
Every step after the worktree registers an undo. When a step fails, or you press Ctrl-C, devkit removes what it made in reverse: the record, the database fork, then the worktree and the branch it created. Submodule problems are warnings, not failures. With --start, a failing lane up keeps the lane and tells you how to retry or remove it.
The Redis prefix warning
A lane uses the site's Redis. Without a per-lane prefix it shares cache keys, sessions and the queue with the site: a lane's worker could run the site's jobs. Put a {{lane.*}} variable in the site's template:
CACHE_PREFIX=webpress{{lane.suffix}}_
REDIS_PREFIX=webpress{{lane.suffix}}_
SESSION_COOKIE=webpress{{lane.suffix}}_session
{{lane.suffix}} is _l1, _l2… in a lane and empty for the site, so the site itself keeps its keys. lane new warns when the template uses no {{lane.*}} variable.
Branches and submodules in a lane
- Commit and push from the lane folder as usual, or use the lane's branch bar in the app, or
devkit git push core-development--crm-fix(submodules first). - Checking out another branch inside a lane works but is warned about: the lane's name no longer matches its branch.
- The lane's Modules and Extensions tabs work as for a site.
devkit modules checkout core-development--crm-fix feat/crm-fix --createputs every submodule on the lane's branch. - The copied
vendor/andnode_modules/are real folders in the lane: rundevkit exec <lane> -- composer installafter changingcomposer.lock, and the site is not affected.
Start, stop and list
devkit lane list # every lane
devkit lane list core-development # one site's lanes
devkit lane up core-development--crm-fix
devkit lane down core-development--crm-fix
lane up and lane down are the same as site up and site down. A lane has its own processes (the site's list until you change the lane's), its own logs and its own Xdebug setting under the artisan engine. In the app, lanes are listed under their site in Sites and Lanes, with the same start, stop and … menu.
Reset a lane's database from its site
When the lane's data has drifted, or the site got new data you want, fork it again:
devkit db reset core-development--crm-fix --yes
devkit db reset core-development--crm-fix --yes --schema-only --safety
The lane's web server and PHP processes stop while the site's current database is forked into a scratch database, which then replaces the lane's; then they start again. --safety keeps a snapshot of the lane's data first (pre_restore_<time>). Migrations the lane ran that the site has not are lost: run them again. Without --yes the command only says what it would do.
In the app: the lane's … menu → Reset database from <site>…, with Schema and data or Schema only, and Snapshot the lane database before resetting (on).
Remove a lane
devkit lane rm core-development--crm-fix --dry-run # what would go, and which work would be lost
devkit lane rm core-development--crm-fix
devkit lane rm core-development--crm-fix --keep-db --delete-branch
lane rm stops the lane, drops its database and its snapshots, removes the worktree, its certificate, logs and .env backups, and frees its ports.
- It refuses with
DIRTY_WORKTREEwhile the lane or a submodule has uncommitted changes (untracked files count) or commits that are on no remote. Commits the lane started from do not count.--forceremoves anyway. --keep-dbkeeps the database and snapshots on the server.--delete-branchalso deletes the local branch. Without it, the branch stays in the site's repository.- Every step tolerates having been done already, so a
lane rmthat failed half way can simply be run again.
In the app: Remove lane… shows what --dry-run reports, with Keep the database and Also delete the local branch (both off), and asks devkit again just before acting.
Database snapshots
A snapshot is a copy of a site's or lane's database on the same server, named <db>__snap_<name>. It is made by the same fast server-side copy as a lane fork, and the site keeps running.
devkit db snapshot shop before_upgrade # name: a-z, 0-9, _ (at most 40)
devkit db snapshot shop # name: the current time, 20261026_143005
devkit db snapshots shop # newest first, with created time and size
devkit db restore shop before_upgrade --yes
devkit db snapshot rm shop before_upgrade --yes
Restore
- A safety snapshot of the current data is taken first (
pre_restore_<time>), so a restore can itself be undone.--safety=falseskips it. - The site's web server and PHP processes stop (Vite keeps running).
- The snapshot is copied into a scratch database, which then replaces the live one.
- What was stopped starts again.
Without --yes, db restore, db reset and db snapshot rm fail with CONFIRM_REQUIRED and say what they would do, changing nothing.
In the app, the site detail's Database tab lists the snapshots with Restore… and Drop…, and Snapshot database… makes one. The Restore sheet has Snapshot the current database first on by default and spells out the three steps; turning it off shows a red warning that the current data cannot be recovered.
Read-only queries
devkit db query shop "SELECT id, email FROM users ORDER BY id DESC LIMIT 5"
devkit db query shop "SHOW TABLES"
One SELECT, SHOW, DESCRIBE or EXPLAIN, run in a read-only transaction and rolled back, returning at most 500 rows. Anything else, a second statement or INTO OUTFILE is refused. It is meant for quick checks and for AI agents; use your usual database client for real work (the stack's port is in devkit stack list).
Limits worth knowing
- Only tables are copied. Views, routines (procedures and functions), triggers, events and sequences are not; devkit lists the ones it left out. Recreate them with a migration if the lane needs them.
- A fork is not taken at one instant. Tables are copied in parallel while the site keeps running, so rows written during the copy can land in one table and not in another. Stop the site's queue and scheduler (or the site) first when you need a perfectly consistent snapshot.
- MySQL and MariaDB only, on the same server: snapshots and forks live next to the original and use its disk.
- At most 99 lanes per site. A lane's number is not reused while devkit still lists the lane.
- Lanes cannot be hidden on their own; hiding a site hides its lanes.
- Deleting a lane's folder by hand leaves its record and database behind (
lane listshows the worktree as missing); rundevkit lane rmto clean up.