devkit Tiếng Việt

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.

PartWhat you getChange it with
Namelower-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
Branchfeat/<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)
Submodulesinitialised 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
Copiesvendor/ and node_modules/, copy-on-write, plus the site's --copy-filesdevkit site set <site> --copy-files …
Database<db>_l<index>, a full copy of the site's database--schema-only, --no-fork-db
Port, URLartisan: its own port, http://localhost:<port>; caddy: https://<lane>.<site>.localhost
.envthe site's template rendered with the lane's database, port and URLthe site's template
Engine, PHP, stackalways the site'son 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 up creates the database. Use it for migrate:fresh from 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 --create puts every submodule on the lane's branch.
  • The copied vendor/ and node_modules/ are real folders in the lane: run devkit exec <lane> -- composer install after changing composer.lock, and the site is not affected.
Copy-on-write is instant and takes no space until files change on APFS (macOS) and btrfs or XFS (Linux). On ext4 it is a normal copy, which takes longer and uses disk space.

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_WORKTREE while 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. --force removes anyway.
  • --keep-db keeps the database and snapshots on the server.
  • --delete-branch also deletes the local branch. Without it, the branch stays in the site's repository.
  • Every step tolerates having been done already, so a lane rm that 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

  1. A safety snapshot of the current data is taken first (pre_restore_<time>), so a restore can itself be undone. --safety=false skips it.
  2. The site's web server and PHP processes stop (Vite keeps running).
  3. The snapshot is copied into a scratch database, which then replaces the live one.
  4. 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 list shows the worktree as missing); run devkit lane rm to clean up.