PHPackages                             synxs-ar/laravel-worktrees - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. synxs-ar/laravel-worktrees

ActiveLibrary[Database &amp; ORM](/categories/database)

synxs-ar/laravel-worktrees
==========================

Persistent, isolated Laravel dev environments ("desks") backed by git worktrees — dynamic free-port resolution for PHP + Vite, per-desk SQLite, unique app keys and storage links.

v1.0.0(2d ago)00MITPHPPHP ^8.1

Since Jun 7Pushed 2d agoCompare

[ Source](https://github.com/synxs-ar/laravel-worktrees)[ Packagist](https://packagist.org/packages/synxs-ar/laravel-worktrees)[ Docs](https://github.com/synxs-ar/laravel-worktrees)[ RSS](/packages/synxs-ar-laravel-worktrees/feed)WikiDiscussions main Synced 2d ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

Laravel Worktrees
=================

[](#laravel-worktrees)

> Persistent, isolated Laravel dev environments — **"desks"** — backed by git worktrees.

Run several branches of the same Laravel app **at the same time**, each with its own port, database, storage and encryption key, without them stepping on each other.

Think of your project as a **workshop** and each worktree as a numbered **desk** (`wt-desk-01`, `wt-desk-02`, …). A desk is a permanent workbench: it keeps its own `vendor/`, `node_modules/`, `APP_KEY`, database and storage. Mount any branch or experiment on a desk, work on it, leave it — the bench stays put, the work comes and goes.

```
php artisan wt:new      # build the next desk
php artisan wt:up 1     # configure (first run) & serve it — PHP + Vite, free ports
```

### Built for parallel coding agents

[](#built-for-parallel-coding-agents)

Modern AI coding agents make it easy to work on several features at once — until they're all editing the *same* checkout. They overwrite each other's files, can't hold separate branches, and fight over a single dev server and database.

`laravel-worktrees` gives each agent — or each feature — its own isolated **desk**: a real git worktree with its own branch, ports, database and storage. Point one agent at `wt-desk-01`, another at `wt-desk-02`, label each with the feature it's building, and they code **in parallel** without ever stepping on one another. It's the foundation for running many branches at the same time — ideal for vibe coding at scale.

---

Why
---

[](#why)

Spinning up a second copy of a Laravel app for a parallel branch is more painful than it should be:

- `php artisan serve` always wants port `8000` → **collisions**.
- Every copy points at the same database → **data bleeds across branches**.
- A fresh worktree has no `.env`, no `vendor/`, no `node_modules/`, no `storage` link, and a stale `vite.config.js` → **manual setup every time**.
- Copy the `.env` by hand and every desk shares one `APP_KEY` → **leaked sessions and encryption between environments**.

`laravel-worktrees` automates all of it behind four commands, and lets you decide — per desk, interactively — whether the database and storage are **isolated** or **shared** with your main checkout.

Requirements
------------

[](#requirements)

- PHP `^8.1`
- Laravel `10`, `11` or `12`
- `git` on the `PATH`
- For an **isolated** database: a reachable **PostgreSQL** (default) or **MySQL** server

Install
-------

[](#install)

```
composer require --dev synxs-ar/laravel-worktrees
```

Optionally publish the config:

```
php artisan vendor:publish --tag=worktrees-config
```

Add the desk folders to your app's `.gitignore`:

```
/worktrees
/.wt-desks
```

Commands
--------

[](#commands)

CommandWhat it does`wt:new [--ref=HEAD]`Provision the next desk (worktree + deps + env). Does **not** touch the database.`wt:up {desk} [label] [--no-vite] [--reconfigure]`Configure the desk on first run (database + storage), then serve **PHP + Vite**. An optional `label` is applied to `APP_NAME`.`wt:label {desk} [label] [--clear]`Set, show or clear a desk's display label.`wt:list`List every desk, its label and preferred-port status.`wt:rm {desk} [--force]`Tear a desk down (worktree + git metadata) and free its slot.`{desk}` accepts the full slug **or** the bare number — `wt:up wt-desk-01` and `wt:up 1` are equivalent.

### Labels

[](#labels)

By default a desk tags its `APP_NAME` with the slug — `MyApp [wt-desk-01]` — so you can tell environments apart in the browser, logs and mail. Give it a human-friendly **label** to make that obvious at a glance:

```
php artisan wt:up 1 checkout-redesign   # APP_NAME -> "MyApp [checkout-redesign]", then serves
php artisan wt:label 1 billing-fix      # rename it any time (no serving)
php artisan wt:label 1                  # show the current label
php artisan wt:label 1 --clear          # back to "MyApp [wt-desk-01]"
```

Labels are remembered in the registry and re-applied on every `wt:up`.

How it works
------------

[](#how-it-works)

### 1. `wt:new` — build the bench

[](#1-wtnew--build-the-bench)

```
✓ Creating git worktree            git worktree add --detach worktrees/wt-desk-01
✓ Installing dependencies (composer)   an isolated vendor/
✓ Installing node modules (npm)        an isolated node_modules/ (if package.json exists)
✓ Materializing .env                   derived from your base .env, APP_NAME tagged [wt-desk-01]
✓ Bootstrapping local config (vite, …) copies gitignored *.example files (e.g. vite.config.js)
✓ Generating APP_KEY                   a UNIQUE key — sessions & encryption stay isolated

```

The worktree is **detached** — a desk is branch-agnostic. Check out whatever you want inside it; the desk's identity (port, database, storage) never moves.

### 2. `wt:up` — choose &amp; serve

[](#2-wtup--choose--serve)

The **first** time you bring a desk up (or any time with `--reconfigure`) it asks you two questions and remembers the answers:

```
? Database for this desk?     [isolated / shared]
    isolated → prompts host / port / user / password / name
               offers to CREATE the database if it doesn't exist yet
               runs your migrations against it
    shared   → uses the project's database from the base .env (never migrated)

? Storage for this desk?      [isolated / shared]
    shared   → media in storage/app/public is shared with your main checkout
               (logs, cache and sessions stay isolated)
    isolated → the desk gets its own storage

```

After that (and on every later `wt:up`) it just serves, on **dynamically resolved free ports**:

```
  PHP  http://127.0.0.1:18001
  Vite http://127.0.0.1:27501

  wt-desk-01 is up. Ctrl+C to stop.

```

`Ctrl+C` cleanly stops the whole process tree (PHP server **and** Vite/esbuild) — no orphans left holding your files.

### Smart ports

[](#smart-ports)

Each desk has *preferred* ports (`base + desk number`, e.g. PHP `18001`, Vite `27501`), but the preferred port is only a hint: `wt:up` probes for a real free port at launch, so a port held by Docker or another desk never blocks you.

All ports stay **above a configurable floor (default `10001`)** — on Windows with Hyper-V / WSL2 / Docker the ranges below 10000 are reserved and throw error `10013` on bind.

### Vite

[](#vite)

If the desk has a `package.json`, `wt:up` runs `npm run dev -- --port  --strictPort` alongside the PHP server. The port is forced via the CLI (which beats `vite.config.js`), so the project needs no config changes. Skip it with `--no-vite`.

Database strategies
-------------------

[](#database-strategies)

Chosen per desk at first `wt:up`:

- **Isolated** — a dedicated database (default driver **`pgsql`**, or `mysql`), created automatically from your base credentials. If the **server** is up but the **database** doesn't exist, you're offered to create it. Then your migrations run against it. Real isolation, your data can't leak.
- **Shared** — the desk uses your project's existing database from the base `.env`. Migrations are **never** auto-run against a shared database.

The isolated database name defaults to `{base}_{slug}` (e.g. `myapp_wt_desk_01`).

> **Why Postgres by default?** PostgreSQL has full `ALTER TABLE` support, so your existing migrations run unchanged. SQLite's limited `ALTER TABLE` (it can't drop a column referenced by a foreign key) breaks many real-world migration suites.

Storage strategies
------------------

[](#storage-strategies)

Chosen per desk at first `wt:up`:

- **Isolated** — the desk has its own `storage/`. `storage:link` as usual.
- **Shared** — the desk's `storage/app/public` is junctioned to your main checkout's, so uploaded media is shared. Logs, cache and sessions stay isolated.

Configuration
-------------

[](#configuration)

`config/worktrees.php`:

KeyEnvDefaultNotes`host``WORKTREES_HOST``127.0.0.1`Bind host for serving &amp; port probing.`port_floor``WORKTREES_PORT_FLOOR``10001`No resolved port goes below this.`php_base_port``WORKTREES_PHP_BASE_PORT``18000`Preferred PHP port = base + desk number.`vite_base_port``WORKTREES_VITE_BASE_PORT``27500`Preferred Vite port = base + desk number.`database.engine``WORKTREES_DB_ENGINE``pgsql`Driver for an isolated database (`pgsql` / `mysql`).`database.name``WORKTREES_DB_NAME``{base}_{slug}`Isolated database name template.`bootstrap_files`—`vite.config.js ⇐ vite.config.example.js`Gitignored files seeded from a committed example.`registry_path`—`.wt-desks/registry.json`The "workshop blueprint" (keep out of git).`worktrees_path`—`worktrees`Where desks are created.`php` / `composer` / `npm``WORKTREES_PHP` / `…COMPOSER` / `…NPM``php` / `composer` / `npm`Binaries used to drive desks.Isolation, the careful bits
---------------------------

[](#isolation-the-careful-bits)

- **Unique `APP_KEY`** per desk, so sessions and encrypted payloads never cross.
- **Environment stripping** — desk child processes are spawned by your main app's artisan command and would otherwise *inherit* its `putenv()`'d values (DB, `APP_KEY`, `APP_URL`); Laravel's immutable Dotenv then refuses to override them. Each desk-owned key is stripped from the inherited environment so the worktree `.env` always wins.
- **`APP_URL`** is written into the desk `.env` to match the resolved port (`php artisan serve` forwards only a whitelist of env vars to its `php -S`worker, so a runtime-injected value never arrives), and also injected into the Vite process (Node *does* inherit, so it must be overridden there).

Windows notes
-------------

[](#windows-notes)

Everything works on Windows out of the box:

- `storage:link` falls back to a directory **junction** (`mklink /J`) when the symlink needs elevation it doesn't have.
- `wt:rm` removes storage junctions as **links** (never following them into their target) and handles `node_modules` paths that exceed `MAX_PATH`.
- `Ctrl+C` terminates the full child process tree (`taskkill /T`).

License
-------

[](#license)

MIT © Synxs

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance99

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

2d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/531346?v=4)[ampz](/maintainers/ampz)[@Ampz](https://github.com/Ampz)

---

Top Contributors

[![Feriksson](https://avatars.githubusercontent.com/u/10608134?v=4)](https://github.com/Feriksson "Feriksson (1 commits)")

---

Tags

laravelsqlitegitviteportsworktreeworktreesdev-environment

### Embed Badge

![Health badge](/badges/synxs-ar-laravel-worktrees/health.svg)

```
[![Health](https://phpackages.com/badges/synxs-ar-laravel-worktrees/health.svg)](https://phpackages.com/packages/synxs-ar-laravel-worktrees)
```

###  Alternatives

[spatie/laravel-health

Monitor the health of a Laravel application

87311.3M149](/packages/spatie-laravel-health)[laravel/dusk

Laravel Dusk provides simple end-to-end testing and browser automation.

1.9k38.6M289](/packages/laravel-dusk)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76318.2M110](/packages/laravel-mcp)[illuminate/queue

The Illuminate Queue package.

20432.2M1.5k](/packages/illuminate-queue)[laravel-doctrine/orm

An integration library for Laravel and Doctrine ORM

8455.5M96](/packages/laravel-doctrine-orm)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
