--- url: 'https://yerd.app/guide/introduction.md' --- # What is Yerd? Yerd is a fast, rootless, open-source local PHP development environment. It serves your projects on `.test` domains over HTTP and HTTPS, runs a different PHP version per site, and manages everything from one small background daemon. No Docker, no `sudo` for daily work, no subscription. If you've used [Laravel Herd](https://herd.laravel.com), you know the appeal: open `https://my-app.test` and it just works - the right PHP version, a trusted certificate, no fuss. Yerd does the same, on macOS and Linux, and it's fully open-source. ## Why Yerd? Setting up local PHP the traditional way means stitching together a web server, a DNS tool, a certificate workflow, and some way to juggle PHP versions. Docker hides the wiring but trades it for image pulls and a VM you didn't want. Yerd gives you the same result as plain, native processes: * **Zero-config sites.** Drop a project into a parked folder and it's instantly live at `.test` - no config files, no virtual hosts. [Sites →](./sites) * **HTTPS that just works.** Yerd issues a trusted certificate for every site on demand: a green padlock, no browser warnings, no `mkcert` dance. [HTTPS →](./https) * **Any PHP version, per site.** Install as many versions as you need and point each project at the one it wants. [PHP versions →](./php-versions) * **Rootless by design.** After a single one-time setup, nothing runs as administrator - the daemon, CLI, and app all run as you. [Elevation →](./elevation) * **Tiny and native.** One small daemon (~8 MB), no containers, no VM, no Electron. PHP builds download only when you ask. * **Batteries included.** Databases and caches (MySQL, MariaDB, PostgreSQL, Redis), mail capture, live Laravel dump streaming, and one-click public [sharing](./sharing) over Cloudflare Tunnel are built in - not bolt-ons. * **Self-diagnosing.** Built-in health checks show what's wrong and repair the safe problems for you. [Diagnostics →](./diagnostics) ## One tool, two ways to drive it Under the hood Yerd is a single background daemon that owns everything - your sites, PHP, HTTPS, and DNS. You drive it however you like: * the **[desktop app](./desktop-app)** - a native tray app (shown above), or * the **[`yerd` command line](../reference/cli/)** - first-class and fully scriptable. Both are just clients of the same daemon, so a change in one shows up in the other immediately - they can never disagree about what's running. ## How it works When you open `https://my-app.test`: 1. Yerd's built-in DNS answers `*.test` with your own machine (`127.0.0.1`). 2. Its reverse proxy matches the address to a site and hands the request to the right PHP version. 3. For secured sites it serves HTTPS using a certificate from Yerd's own local certificate authority - trusted once, valid everywhere. That's the whole path, and it all lives inside the one daemon. Curious about the internals? See the [Architecture](../developer/architecture) overview. ## A quick taste ```sh yerd install php 8.5 # grab a PHP version yerd park ~/Sites # serve every project in a folder yerd secure my-app # turn on trusted HTTPS # open https://my-app.test - done ``` The [Getting Started](./getting-started) guide walks through it from a clean machine. ## Who Yerd is for * **PHP and Laravel developers** who want Herd-style `.test` sites and trusted HTTPS on macOS or Linux. * **Anyone juggling multiple PHP versions** who needs a specific one per project. * **People who'd rather not run Docker** for local dev and prefer light, native processes. * **Open-source-minded developers** who want a tool they can read, audit, and contribute to. ## Next steps * [Getting Started](./getting-started) - install Yerd and serve your first site. * [Features](./desktop-app) - a tour of everything Yerd can do, screen by screen. --- --- url: 'https://yerd.app/guide/getting-started.md' description: >- Install the Yerd desktop app and use its first-run onboarding journey to get from a clean machine to your first site live at https://my-app.test. --- # Getting Started This guide takes you from a clean machine to your first site live at `https://my-app.test`, using the **Yerd desktop app** - the recommended, GUI-first way to run Yerd. Yerd runs entirely **as your user**. `sudo` shows up in exactly two non-ongoing places: installing the system package (standard for any `.deb`/`.pkg.tar.zst`/`.rpm`), and a single, optional, **one-time** privileged setup step that the app walks you through. Day-to-day use never touches root. ::: info Supported platforms Yerd ships a single desktop app for **macOS** (Apple Silicon) and **Linux** (Debian/Ubuntu `.deb` for x86-64 and arm64, an Arch `.pkg.tar.zst` for x86-64, and a Fedora `.rpm` for x86-64 and arm64). The daemon, the `yerd` CLI, and the privileged helper are all bundled inside it - there is nothing else to install. PHP itself is **not** bundled - Yerd downloads prebuilt static PHP builds on demand once you pick a version, so the install stays tiny and fast. ::: ::: warning Apple Intel not supported Intel (x86-64) Macs are not supported at this time. macOS builds target Apple Silicon (arm64) only. ::: ## Install Grab the latest **stable release** from the [releases page](https://github.com/forjedio/yerd/releases): | Platform | Download | Install | |---|---|---| | macOS (Apple Silicon) | `Yerd_MacOS_AppleSilicon_v.dmg` | open, drag Yerd to Applications | | Linux · Debian/Ubuntu (x86-64) | `Yerd_Linux_x86_64_v.deb` | `sudo apt install ./Yerd_Linux_x86_64_v.deb` | | Linux · Debian/Ubuntu (arm64) | `Yerd_Linux_Arm64_v.deb` | `sudo apt install ./Yerd_Linux_Arm64_v.deb` | | Linux · Arch (x86-64) | `Yerd_Linux_x86_64_v.pkg.tar.zst` | `sudo pacman -U ./Yerd_Linux_x86_64_v.pkg.tar.zst` | | Linux · Fedora (x86-64) | `Yerd_Linux_x86_64_v.rpm` | `sudo dnf install ./Yerd_Linux_x86_64_v.rpm` | | Linux · Fedora (arm64) | `Yerd_Linux_Arm64_v.rpm` | `sudo dnf install ./Yerd_Linux_Arm64_v.rpm` | ::: tip Arch Linux Remove any leftover `/usr/bin/yerd` from the old v1 (Go) project first - pacman won't install over a file it doesn't own - and `pacman -Syu` before installing so the bundled GUI's WebKit/GTK libraries match your system. If you are upgrading from Yerd v2.0.3 or earlier, in-app update cannot see this release, so reinstall once by hand with the same `sudo pacman -U ./Yerd_Linux_x86_64_v.pkg.tar.zst` command from the [releases page](https://github.com/forjedio/yerd/releases); your settings and sites are preserved, since it is a normal package upgrade, and in-app updates work again from this version on. ::: The macOS `.dmg` installer window - drag **Yerd** onto **Applications** to install. On macOS that makes setup essentially **drag-and-drop**: drag Yerd to Applications and launch it. On Linux the package puts `yerd` on your `PATH` automatically. Everything else below happens inside the app. ::: tip Prefer the terminal, or building from source? Everything in this guide has a `yerd` CLI equivalent - see the [CLI Reference](../reference/cli/). The app comes with the CLI bundled: open **Settings → Terminal CLI** and click **Install** on macOS (the Linux package puts it on `PATH` automatically). To build and run from source instead, see [Building from Source](../developer/building). ::: ## First launch: the onboarding journey The first time you open Yerd on a fresh machine, it greets you with a short, guided **onboarding journey** instead of dropping you straight into the dashboard. It walks you through the handful of one-time steps that turn a clean install into a working `.test` environment: starting the daemon, installing a PHP version, pointing Yerd at your projects, and granting the OS privileges for HTTPS and ports 80/443. Every step except the daemon install has a **Skip for now**, and you can move **Back** at any point - nothing you skip is lost, it just lives on its normal page in the app. Following it end to end gets you from install to serving sites in a couple of minutes. ### Step 1 - Install the daemon The journey opens by introducing **`yerdd`**, the small background service that does all the real work - it supervises PHP-FPM, serves your `.test` sites over HTTP/HTTPS, answers DNS, and runs databases. The app is just a client of it and **never runs as root**. Click **Install & start daemon**. The button keeps spinning until the daemon actually connects, then turns into a green **Running** badge and **Continue** unlocks. This is the one required step - everything after it is skippable. Installing the daemon here also sets sensible login defaults: the **daemon and the app both start at login**, with the **app started minimized** to the tray. Change any of the three later under **Settings → Start at login**. ::: tip macOS background approval On macOS the daemon registers as a background **SMAppService** login item (it shows as "Yerd" in System Settings → Login Items). If macOS asks you to approve it first, the step shows an **Open Login Items** button to take you there; once approved it connects automatically. ::: ### Step 2 - Install a PHP version Pick a PHP version to install - the **latest** is selected for you, and the **first version you install automatically becomes your default**. It downloads a prebuilt, self-contained build (this can take a minute or two with no progress bar). Add or change versions any time later on the [PHP page](./php-versions). Not ready? Click **Skip for now** and install one later. ### Step 3 - Park a projects folder Point Yerd at a folder of projects and every subfolder is served automatically at `.test`. Click **Choose a folder…**, pick your `~/Sites` (or wherever your projects live), and you're done. This is the fastest way to get many sites at once; you can also link individual projects later. See [Sites](./sites) for the difference between parking and linking. Skippable - park a folder whenever you're ready. ### Step 4 - Trust & system access For HTTPS on `.test` and serving on the standard ports 80/443, Yerd needs three OS-level privileges: * **Trust the local CA** so browsers accept your `.test` HTTPS certificates without warnings. * **Install the `.test` resolver** so `*.test` names resolve to Yerd. * **Bind privileged ports 80/443** (otherwise Yerd falls back to `8080`/`8443`). Use **Fix all** to grant them in one go - you'll be asked for your password by the OS. This step is optional; you can do it later from the [Doctor page](./diagnostics), and Yerd works on high ports until you do. For exactly what runs and why it's safe, see [Elevation & Privileges](./elevation). ::: tip Reverting later Anything you grant here is reversible from Doctor (or `sudo yerd unelevate`). See [Elevation & Privileges](./elevation) for details. ::: ### Step 5 - You're all set That's it. Click **Get started** and Yerd marks setup complete and drops you on the **Overview** dashboard. The journey won't show again on this machine - next time the app opens straight into the dashboard, or the **Start Yerd** screen if the daemon happens to be stopped. If you ever want to see it again, run [`yerd uninstall`](../reference/cli/uninstall) to reset to a clean state, then reopen the app. See the [Features](./desktop-app) guide for the full tour of every page. ## Serve, secure, and check on your first site Whatever you skipped in the journey lives on its normal page in the app: * **PHP** - install or switch versions, set a global default, or pin one site. * **Sites** - park a folder, link a single project, and toggle HTTPS per site. * **Doctor** - grant or revert the CA/resolver/ports privileges, and see a health check with one-click fixes. Open `https://my-app.test` once it's parked or linked and secured - that's it. ## Uninstall To remove Yerd completely, run the bare `uninstall` command (no subcommand) from a terminal. It prompts for confirmation, then tears down the daemon, the PATH entry, all config/data/downloads, and the binaries: ```sh sudo yerd uninstall # recommended - also reverts the one-time elevate changes yerd uninstall # without root - removes everything except the elevate changes ``` Run it with **`sudo`** so it can also reverse the `elevate` system changes (the CA in your trust store, the `*.test` resolver, and the macOS port redirect). Those need root to undo, and they **can't** be undone once the binaries are gone - so without `sudo`, yerd warns you and prints the exact manual commands to clean them up later. Add `--yes` to skip the prompt in scripts. A `.deb` install is removed the usual way (`sudo apt purge yerd`); the macOS app is dragged to the Trash. Full details in the [Uninstall reference](../reference/cli/uninstall). ## Where to next * [Features](./desktop-app) - a tour of every page in the app. * [Sites](./sites) - parking, linking, and per-site settings in depth. * [PHP Versions](./php-versions) - pools, ini defaults, and updates. * [HTTPS & Certificates](./https) and [DNS & .test Domains](./dns). * [Elevation & Privileges](./elevation) - exactly what the one-time setup grants. * [CLI Reference](../reference/cli/) and [Configuration Reference](../reference/configuration) for the full surface. --- --- url: 'https://yerd.app/guide/guides.md' description: >- Short video walkthroughs of installing Yerd, setting up services and tooling, and creating your first Laravel site. --- # Guides Prefer to watch instead of read? These short videos walk through the same ground as the written guides - installing Yerd, serving your first site, and setting up the extras. ## Installing Yerd Download the app, install it, and get through the first-run onboarding journey. See the written version in [Getting Started](./getting-started). ## Services & additional tooling Databases, caches, and the developer tools (Composer, Node, Bun) Yerd can install and supervise for you. See [Services & Databases](./services) and [Tooling](./tooling). ## Creating a new Laravel site Park or link a project and serve it at its own `.test` domain. See [Sites](./sites) for the full walkthrough. --- --- url: 'https://yerd.app/guide/switching-to-yerd.md' description: >- A step-by-step guide to switching to Yerd from Laravel Herd, Valet, or Lerd - stop the other tool's services first, elevate Yerd, verify, and how to switch back cleanly. --- # Switching to Yerd Yerd, [Laravel Herd](https://herd.laravel.com), [Valet](https://laravel.com/docs/valet), and [Lerd](https://github.com/geodro/lerd) all do the same OS-level job for local development, which means they all reach for the **same three system resources**: * **Ports 80 and 443** - only one process can listen on each at a time. * **The `*.test` resolver** - one OS resolver route per TLD (`/etc/resolver/test` on macOS, or systemd-resolved/NetworkManager snippets on Linux). * **A trusted local CA** - each tool installs its own into the system trust store. Because those are single-owner, **two tools can't run at once** without fighting over ports and DNS. Switching is therefore mostly about handing those three things over cleanly: stop the old tool, let Yerd take the ports/resolver, and (if you ever go back) hand them back. ::: tip The one rule **Stop the other tool's services before you `sudo yerd elevate`.** Everything else is detail. ::: This guide is for switching *machines you already use* for PHP dev. On a clean machine there's nothing to migrate - just follow [Getting Started](./getting-started). ## Before you start * **Your project code is safe.** None of these tools own your source - they just serve folders. Switching changes *what's serving* `*.test`, not your files. You don't need to move or copy any project. * **PHP versions don't carry over.** Yerd installs its own [prebuilt PHP builds](./php-versions); it doesn't reuse Herd/Valet/Homebrew PHP. [Getting Started](./getting-started) walks through installing the version(s) you need. * **You can keep the old tool installed.** You don't have to uninstall Herd/Valet/Lerd to try Yerd - you just can't have both *serving* at the same time. Keeping it installed makes switching back trivial. ::: warning Subdomains are apex-only by default If you relied on Valet/Herd resolving *any* subdomain of a site (`api.my-app.test`, `admin.my-app.test`, ...) implicitly, note that in Yerd a site answers only its exact apex `my-app.test` by default. Register the ones you need explicitly, or re-add the old catch-all behaviour with `yerd domain add my-app '*.my-app.test'`. See the [domains reference](../reference/cli/domains). ::: ## Step 1 - Stop the other tool Stop whatever currently serves `*.test` so it releases ports 80/443 and stops answering `.test` DNS. Pick your tool: ### From Laravel Herd Herd runs `nginx` + `dnsmasq` as background services. * **Quit Herd** (or, in the app, pause/stop its services), **or** from the terminal: ```sh herd stop # stop Herd's nginx/php/dnsmasq services ``` * Herd may relaunch its services on login. If you're switching for good, quit Herd fully (and consider removing it from Login Items) so it doesn't grab port 80 again on your next reboot. ### From Laravel Valet Valet runs `nginx` + `dnsmasq` via Homebrew. ```sh valet stop # stop nginx + dnsmasq (releases 80/443 and *.test DNS) ``` To go further and have Valet remove its own DNS/loopback hooks too: ```sh valet uninstall # removes Valet's nginx/dnsmasq config (keeps your sites list) ``` `valet stop` is enough to switch; `valet uninstall` is for a permanent move. ### From Lerd Lerd runs your stack in **rootless Podman containers**. ```sh lerd stop # stop Lerd's containers (frees 80/443) ``` If a stray container still holds a port, stop it directly with `podman stop ` (or `podman ps` to find it). ### Any other tool The principle is the same: **stop whatever is listening on 80/443 and answering `*.test`.** To find a squatter on the ports: ```sh # macOS / Linux - who holds 80 and 443? sudo lsof -nP -iTCP:80 -sTCP:LISTEN sudo lsof -nP -iTCP:443 -sTCP:LISTEN ``` ## Step 2 - Install Yerd With the other tool stopped, follow **[Getting Started](./getting-started)** to install Yerd and go through its first-run onboarding journey - it installs and starts the daemon, installs a PHP version, and parks a projects folder, all from the app. You can do all of this **before** elevating: without elevation Yerd serves on the [rootless fallback ports](./elevation#the-rootless-fallback) `8080`/`8443`, so you can sanity-check it (`http://my-app.test:8080`) while the old tool is stopped. ## Step 3 - Elevate Yerd Hand the three system hooks to Yerd. In the desktop app this is the **Doctor → Fix all** button; from the terminal: ```sh sudo yerd elevate # trust the CA · route *.test · allow 80/443 ``` This trusts Yerd's local CA, points the `*.test` resolver at Yerd's DNS, and lets the daemon serve on 80/443 (a `pf` redirect on macOS, `setcap` on Linux). See [Elevation & Privileges](./elevation) for exactly what each step does. ::: tip macOS backs up your old resolver On macOS, if a `/etc/resolver/test` already exists (a Valet/Herd leftover), `yerd elevate resolver` **saves a backup** before replacing it - so [switching back](#switching-back) can restore your previous DNS exactly. Linux has no backup mechanism; the drop-in is simply added/removed. ::: ## Step 4 - Verify ```sh yerd doctor # checks CA trust, the *.test resolver, ports, PHP, and sites ``` `doctor` is the source of truth for a clean switch. Watch for: * **`ForeignWebListener`** - something *other than Yerd* is still holding 80 or 443. That's the old tool not fully stopped (go back to [Step 1](#step-1-stop-the-other-tool)). Yerd detects this by checking for its own `Server: yerd` marker on the port. * **Ports fell back to 8080/8443** - elevation didn't take the privileged ports (often the same cause). Re-stop the other tool and re-run `sudo yerd elevate ports`. * **Resolver / CA not configured** - re-run the matching `sudo yerd elevate `. Then open `https://my-app.test` - padlock and all. ::: warning Flush DNS / restart the browser After the resolver changes, your OS or browser may still cache the old answer (or an HSTS/redirect from the previous tool). If a `.test` site won't resolve or shows a cert warning: ```sh # macOS - flush the DNS cache sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder ``` On Linux, Yerd reloads the detected supported resolver manager for you. Restart the browser if a site is stuck on the old CA or an HTTPS redirect. ::: ## Running side by side (without switching) If you only want to *try* Yerd without giving up your current tool, **don't elevate**. Leave the other tool owning 80/443 and `*.test`, and reach Yerd's sites on its rootless ports instead: * `http://my-app.test:8080` / `https://my-app.test:8443` This avoids any conflict - but it's a trial mode, not the "just type the URL" experience. To make Yerd the default, stop the other tool and elevate. ## Switching back Switching back is the reverse: hand the three hooks back to the other tool. 1. **Un-elevate Yerd** so it releases the resolver, CA trust, and ports: ```sh sudo yerd unelevate # revert all three ``` * On **macOS**, `unelevate resolver` **restores the resolver backup** taken in Step 3 (returning DNS to its pre-Yerd state), and `unelevate ports` removes the `pf` redirect. * On **Linux**, it removes all Yerd-owned systemd-resolved and NetworkManager resolver snippets. `setcap` has no clean reverse, so `unelevate ports` just prints the manual command (`sudo setcap -r `) - harmless to leave, but run it if you want it gone. 2. **Stop Yerd's daemon** so it isn't holding ports when the other tool starts: ```sh yerd stop # or quit the desktop app (which stops the daemon) ``` 3. **Start the other tool's services again** and let it re-claim the ports, DNS, and its own CA: ```sh valet start # Valet (or `valet install` if you uninstalled it) # herd start / launch Herd # lerd start ``` 4. **Flush DNS** (see the warning above) and restart your browser. ::: tip Both CAs trusted is fine Switching back doesn't *require* removing Yerd's CA - having more than one trusted local CA in your system store is harmless (the browser trusts certs from any of them). `sudo yerd unelevate trust` removes Yerd's if you want a tidy store; the helper only ever removes the cert whose subject is `Yerd Local CA`, never anyone else's root. ::: ## Removing Yerd entirely If you're not coming back, do a full uninstall - which runs the same `unelevate` (all three targets) **before** deleting the binaries, so the system changes are reversed while `yerd-helper` still exists to reverse them: ```sh sudo yerd uninstall # un-elevates, then removes daemon, config, data, binaries ``` Run it **with `sudo`** - the trust/resolver/port changes can't be undone once the helper is gone. See the [Uninstall reference](../reference/cli/uninstall). ## See also * [Getting Started](./getting-started) - install and first site * [Elevation & Privileges](./elevation) - exactly what `elevate`/`unelevate` change * [DNS & .test Domains](./dns) - how `*.test` resolution works * [HTTPS & Certificates](./https) - the local CA and per-site certs * [Diagnostics](./diagnostics) - `yerd doctor` and what it checks --- --- url: 'https://yerd.app/guide/upgrading-from-v1.md' --- # Upgrade Guide Coming from the original Go-based Yerd (`LumoSolutions/yerd`, "v1")? This page covers what changed and how to move your local sites to v2. ::: warning This is a replacement, not an in-place upgrade. Yerd v2 (`forjedio/yerd`) is a ground-up Rust rewrite. Different binaries, a different on-disk layout, an incompatible config format, and a redesigned command set. There is no automatic migration. You uninstall v1 and set up v2 fresh. ::: ## What changed | Area | v1 (Go) | v2 (Rust) | |---|---|---| | Privileges | leaned on `sudo` for most operations | rootless; elevates once, then runs as your user | | PHP | built from source | prebuilt, signed static builds downloaded on demand | | Platforms | - | macOS + Linux today (Windows on the [roadmap](./services)) | | Config & layout | v1's format | new, incompatible TOML config and layout | | Commands | v1's names | redesigned; don't assume names carry over | So v2 installs small and fast (no compiler needed for PHP), doesn't ask for your password every time you touch a site, and behaves the same across operating systems. ::: info Rootless by design `sudo` appears in only two places: installing the system package (normal for any `.deb`/`.pkg.tar.zst`), and the one-time `sudo yerd elevate`. After that, day-to-day use never touches root. See [Elevation & Privileges](./elevation). ::: ## What does not carry over * **v1 config is not read.** v2 uses its own TOML config; see the [Configuration Reference](../reference/configuration). * **PHP versions are not reused.** Reinstall the versions you need (fast, no compilation). * **Sites are not migrated.** Re-park and re-link them with the v2 commands below. * **Command names may differ.** Use the [CLI Reference](../reference/cli/) or `yerd --help`. * **The local CA is regenerated.** You trust the new CA once during setup. ## Migration, step by step ### 1. Stop and remove the v1 install Shut down anything v1 is running so it can't fight v2 over ports 80/443, the DNS resolver, or your `.test` domains. Stop its daemon/service, then uninstall the Go binary and its files. If v1 changed your system DNS resolver or installed a CA into your trust store, undo those too (v1's own uninstall is the right tool). A stale resolver or trusted CA is the most common cause of "it half-works" symptoms after switching. ::: tip After removing v1, reboot or flush DNS so the OS forgets v1's resolver before v2 installs its own. ::: ### 2. Install Yerd v2 Follow **[Getting Started](./getting-started)** to install the app and go through its first-run onboarding journey - it installs and starts the daemon for you. You can install a PHP version and park a projects folder there too, or hold off and do it explicitly in the migration-specific steps below. ### 3. Run the one-time privileged setup The only command that uses root in normal use. It trusts the local CA, routes `*.test` to Yerd's DNS responder, and lets the daemon bind ports 80/443: ```sh sudo yerd elevate ``` You can grant the pieces individually: ```sh sudo yerd elevate trust # trust the local CA in the system store sudo yerd elevate resolver # route *.test queries to Yerd's DNS responder sudo yerd elevate ports # allow the daemon to bind ports 80/443 ``` Reverse any of these with `sudo yerd unelevate` (optionally `trust`, `resolver`, or `ports`). See [Elevation & Privileges](./elevation). ::: tip Ports without elevation Skip `sudo yerd elevate ports` and the daemon falls back to `8080`/`8443`. `yerd doctor` reports what's in effect. ::: ### 4. Reinstall the PHP versions you need These download prebuilt static builds, so it's quick: ```sh yerd install php 8.5 yerd install php 8.3 ``` Set a global default (this drives the terminal `php` shim and the per-site fallback): ```sh yerd use 8.5 ``` Check what's installed and what updates are available: ```sh yerd list php # installed versions + the global default yerd list php --available # versions installable from the distribution yerd list php --check # refresh "update available" status (polls now) ``` See [PHP Versions](./php-versions) for installs, updates, and per-site pinning. ### 5. Re-park and re-link your sites To turn a directory whose sub-folders should each become a `.test` site, **park** it: ```sh yerd park ~/Sites # ~/Sites/blog -> http://blog.test ``` For a single project served under a name you choose, **link** it: ```sh yerd link my-app ~/code/my-app # -> http://my-app.test ``` Verify: ```sh yerd sites # every parked or linked site yerd list parked # the registered parked directory roots ``` Later, `yerd unlink ` removes a linked site and `yerd unpark ` un-parks a directory. See [Sites](./sites). ### 6. Turn HTTPS back on per site HTTPS isn't on by default. Promote the sites that need it: ```sh yerd secure my-app # -> https://my-app.test (trusted, via the local CA) ``` Use `yerd unsecure ` to turn it off. Since the CA was trusted in step 3, secured sites get a green padlock with no browser warnings. See [HTTPS & Certificates](./https). ### 7. Pin per-site PHP versions (optional) The two-argument form of `yerd use` targets a single site: ```sh yerd use my-app 8.3 ``` ### 8. Confirm everything is healthy ```sh yerd status # daemon, proxy, DNS, ports, CA, PHP health yerd doctor # diagnose common problems yerd doctor fix # attempt safe, unprivileged repairs ``` `yerd doctor` is most useful right after migrating: it flags a left-over v1 resolver, a port conflict, or an untrusted CA and tells you what to do. Add `--json` for machine-readable output. See [Diagnostics](./diagnostics). ## Command map Commands you'll use most while migrating. For the rest, see `yerd --help` and the [CLI Reference](../reference/cli/). | Task | v2 command | |---|---| | Park a directory of projects | `yerd park ` | | Link one project as a named site | `yerd link ` | | Remove a linked site | `yerd unlink ` | | Un-park a directory | `yerd unpark ` | | List sites | `yerd sites` | | List parked roots | `yerd list parked` | | Install a PHP version | `yerd install php ` | | Set the global PHP default | `yerd use ` | | Pin a site's PHP version | `yerd use ` | | List / update PHP | `yerd list php` · `yerd update php []` | | HTTPS on / off | `yerd secure ` · `yerd unsecure ` | | Manage a site's domains | `yerd domain list\|add\|remove\|primary\|reset ` | | One-time privileged setup | `sudo yerd elevate [trust\|resolver\|ports]` | | Reverse setup | `sudo yerd unelevate [...]` | | Health & repair | `yerd status` · `yerd doctor` · `yerd doctor fix` | v2 lets a site answer multiple domains, subdomains, and wildcards through `yerd domain` (see the [domains reference](../reference/cli/domains)). Unlike some setups, subdomains are explicit: a site answers only its exact apex until you add more. ::: details Lineage Yerd v2 is a ground-up rewrite of our own v1 package ([`LumoSolutions/yerd`](https://github.com/LumoSolutions/yerd)). v1 is reference-only: no command-surface or config-format compatibility. The full project lives at [github.com/forjedio/yerd](https://github.com/forjedio/yerd). ::: ## Where to go next * New to the concepts? See the [Introduction](./introduction) and [Features](./desktop-app). * Setting up from scratch: [Getting Started](./getting-started). * The full command list: [CLI Reference](../reference/cli/). * The new config format and layout: [Configuration Reference](../reference/configuration). --- --- url: 'https://yerd.app/guide/desktop-app.md' description: >- A tour of the Yerd desktop app, screen by screen - the recommended way to install and run Yerd, a fast, rootless, open-source local PHP environment for macOS and Linux. --- # Features Yerd is a fast, rootless, open-source local PHP environment for macOS and Linux. It serves projects on `.test` domains over HTTP and HTTPS, runs a different PHP version per site, and manages it all from one small daemon. No Docker, no `sudo` for daily work, no subscription. The **desktop app** is the recommended way to run all of it: a small tray-first window over everything the CLI does. Built with Tauri v2, Vue 3, TypeScript, and Tailwind, it's a thin client of the [daemon](./daemon), just like the `yerd` CLI - every button maps to one IPC request to `yerdd`, so the GUI and CLI can't drift out of sync. This page is a tour of everything it can do, screen by screen; each section links to the full guide for that feature. If you live in the terminal, the [CLI](../reference/cli/) is a first-class alternative. Not installed yet? See [Getting Started](./getting-started). ## The window at a glance The sidebar opens on **Overview** and groups the rest: | Group | Pages | | --- | --- | | (top) | **Overview** - a live dashboard of what's running | | Environment | **PHP** · **Sites** | | Developer | **Tooling** · **Services** · **Mail** · **Dumps** | | Integrations | **Share** - publish a site over a public URL ([guide](./sharing)) | | System | **Settings** · **Doctor** · **About** | ### Overview The landing dashboard. With the daemon running it shows a **serving** summary - the number of live `.test` sites (each a clickable chip that opens in your browser), stat tiles for PHP versions, sites, services, and captured mail (each links to its page), and a **system-health** strip (Local CA, `.test` resolver, privileged ports). When the daemon is down, the same surface becomes a **Start Yerd** hero. While the daemon is running, this page polls it every 5 seconds, so a change made from the CLI or another window shows up without a manual refresh. ### Settings App- and daemon-level settings (one of the pages that stays usable when the daemon is down, since it can start or install it): * **Daemon.** Whether `yerdd` is running (with pid), a Start or Stop button, and a list of the daemon's in-process subsystems - the DNS resolver, the HTTP and HTTPS proxy listeners (with bound ports, including when macOS's `pf` redirect carries `:80`/`:443`), **Mail capture** (by port), and **Dump capture** (by port). The daemon row has a Restart button. Start/Stop/Restart go through your per-user service manager (systemd `--user` on Linux, a launchd LaunchAgent on macOS), with a detached-process fallback where none exists; the same actions are in the tray menu. * **Application Ports** (while the daemon is running). Editable HTTP/HTTPS (the rootless fallback ports used when 80/443 need elevation), DNS, mail-capture, and dumps ports. If a port is in use elsewhere the page flags it here (site serving or `.test` resolution shows as unbound) so you can pick a free one. Change a value and **Save & restart** validates it, saves, restarts the daemon, and rechecks. HTTP/HTTPS are locked while ports are elevated - un-elevate them on the Doctor page first. * **Start at login.** Three toggles - start the daemon at login, start the app at login, and start the app minimized (hidden to the tray). The daemon-at-login toggle is disabled where no per-user service manager is available. * **Terminal CLI** (macOS and Linux). Installs `yerd` - and your installed tools (`php`, `composer`, ...) - onto your shell `PATH`. On a packaged Linux install `yerd` itself is already on `PATH`, so this is mainly how Linux users get the PHP/tool shims on `PATH` too. * **Appearance.** A System / Light / Dark theme selector; a **Tray icon** selector (Automatic, Light Y, Dark Y, Full icon) for the menu bar / system tray icon; and a **Title bar** selector (Automatic, macOS, Linux, Linux (Reversed), Windows) that forces a window-control style regardless of host platform. All three apply live and are remembered across launches. ### PHP Manages your installed [PHP versions](./php-versions): * A table of installed versions showing live FPM pool state, patch level, pool memory (RSS), and whether an update is available. * Install opens a picker of installable versions (already-installed ones are hidden). Installs download a prebuilt static build; progress streams live next to the Install button as it happens. * Refresh re-checks for updates. Update all updates every version with a pending update. Updates are notify-only. * Each row's `⋯` menu offers Restart (only when the pool is running or failed), Update (only when available), Set default (marks it with a star), and Uninstall. Restart all restarts every running pool. * A Default settings card edits the global ini defaults applied to every version: `memory_limit`, `max_execution_time`, `max_input_time`, `max_file_uploads`, `upload_max_filesize`, `post_max_size`, `error_reporting`, and `display_errors`. Leave a field blank to use PHP's built-in default. Saving restarts running pools to apply. * A Per-version configuration card lists your versions down the side, newest first. Picking one scopes everything to that version: a settings form overriding the defaults above (empty fields inherit), its custom extensions, and free-form ini directives. See [Per-version configuration](./php-versions#per-version-configuration). * The Extensions section of that card registers extra `.so` extensions for the selected version (loaded into both FPM and the CLI): Add… browses for the file, optionally names it and toggles Zend extension. Each is load-probed before saving, and any whose file has gone missing is flagged. See [Custom extensions](./php-versions#custom-extensions). ### Sites The home base for [managing sites](./sites). Polls the daemon every 5 seconds while running, so sites added or changed elsewhere show up without a manual refresh. Two cards: Parked folders. Each parked directory shows a count of the `.test` sites it produces (one per child directory). Park folder opens a native directory picker; each row's menu offers Reveal folder or Un-park (with confirmation). Sites. Every parked and linked site is a card: the `name.test` URL (click to open in your browser), the document root, and badges for kind (`parked`/`linked`), PHP version, HTTPS/HTTP, and the [served web root](./sites#web-root-the-served-directory) when it isn't the project root. A `+N` badge appears when the site answers extra domains beyond its apex, and an amber warning shows when another site claims this site's apex. Each card's `⋯` menu offers **Edit…**, **Manage domains…** (set the primary domain, add or remove aliases and wildcards), Open in browser, Reveal folder, **Share publicly…** (jumps to the [Share page](#share)), and (linked sites only) Unlink. **Edit…** opens one dialog covering everything about the site: PHP version, web root (blank means auto-detect), the HTTPS toggle, and its [group](./sites#site-groups). Parked sites have no destructive action here; remove them by un-parking their folder, or they'd reappear. Sites can also be organized into named, reorderable groups shown as collapsible sections on this page; see [Sites](./sites) for the full walkthrough. ::: tip Untrusted CA banner If your local CA isn't trusted in the system store, the Sites view shows a banner (browsers will warn on HTTPS sites until fixed). It links to the **Doctor** page's Environment panel, where one click runs the fix. See [HTTPS & Certificates](./https). ::: ### Tooling Installs self-contained developer tools - Composer, Node, and Bun - onto your PATH alongside PHP, each managed by Yerd (install / update / uninstall) so they don't collide with system installs. See [Tooling](./tooling). ### Services The database and cache engines Yerd supervises - Redis (Valkey), MySQL, MariaDB, and PostgreSQL. Install a version, then Start / Stop / Restart it. Each installed engine's `⋯` menu also offers **Configuration** (copy the Laravel `.env` for that engine - with a database picker that pre-fills `DB_DATABASE` for SQL engines), Edit port, View logs, **Manage databases** (create / drop / back up / restore, SQL engines only), Change version, and Uninstall. The daemon **auto-starts every installed engine** on boot. See [Services & Databases](./services). ### Mail The built-in SMTP **mail capture** server - point your app's mailer at `127.0.0.1` on the shown port and every outgoing email is captured for preview instead of being sent. Toggle capture, set the port, and open the separate **Mails** viewer with Show Mails. A **Laravel configuration** card emits the `.env` mail keys (`MAIL_HOST`, `MAIL_PORT`, …) to paste into your app, with editable From name/address. Captured mail is tracked read/unread: the sidebar **Mail** item shows an unread-count pill (click it to jump straight to the viewer), and opening a message marks it read. See [Mail Capture](./mail). ### Dumps Laravel telemetry interception - `dump()`/`dd()` plus queries, jobs, views, requests, logs, cache, and outgoing HTTP - streamed to a separate viewer window with no code changes, captured by a per-version PHP extension. Enable interception, pick which signals to record, set the port, and open the viewer with Show Dumps. See [Laravel ▸ Dumps](./laravel-dumps). ### Share Publishes a local site to the public internet over Cloudflare Tunnel. A **Cloudflare Tunnel** card shows the detected `cloudflared` version; a **Shared sites** card picks a site and opens a Quick Tunnel with one click, alongside a live table of active tunnels. A separate **Named tunnels** card walks through connecting a Cloudflare account and exposing sites on your own domain. See [Sharing Sites](./sharing). ### Doctor Mirrors [`yerd doctor`](./diagnostics): * **Health.** Lists problems by severity (Healthy / Warning / Problem) with a copyable remedy command. Run safe fixes applies the safe one-click fixes; Re-check re-runs diagnostics. A clean machine shows an "all clear" panel. * **Environment.** OS-level state: Local CA trusted, `.test` resolver installed, and Privileged ports (80/443). A Fix (elevate) button runs the privileged action where a row isn't configured; once a row *is* configured, an **Unelevate** button reverts it - behind an in-app confirm dialog and the OS prompt. Unelevating the `.test` resolver restores your previous resolver on macOS; reverting privileged ports is macOS-only (Linux `setcap` has no clean reverse, so no button is shown there). ::: info "Fix" actions never run the GUI as root The Fix buttons run the audited `yerd elevate` helper under an OS prompt; the GUI never runs elevated. On Linux this uses `pkexec`, on macOS an `osascript … with administrator privileges` prompt. You may be asked for your password. See [Elevation & Privileges](./elevation). ::: ### About Shows the app, daemon, and negotiated IPC protocol versions, plus your local environment: the TLD (`.test`), the DNS responder address, and the local CA certificate path and fingerprint (both copyable, with reveal-in-finder). It also links to the project repository. * **Updates.** A release-channel selector (Stable / Edge pre-releases), a **Check now** button, and the last-checked status (current version, latest stable/edge, and how long ago it checked). When an update is available, an **Apply update** button downloads, verifies, and installs it, restarting the app. * **Troubleshooting.** **Logs** opens a dialog tailing the GUI's own session log (`yerd-gui.log`) alongside the daemon log, tab-switchable, with a copy button. **Diagnostics** gathers a shareable text snapshot of app/daemon state with its own copy button - useful when reporting a problem. ## Keyboard shortcuts The window is fully keyboard-driven. Shortcuts follow each platform's convention: where macOS uses **Cmd** (`⌘`), Linux uses **Ctrl** with the same letter. Two of them are all you need to remember - the **command palette** (`⌘K` / `Ctrl+K`) jumps to any page or runs any action by typing, and the **shortcuts** overlay (`⌘/` / `Ctrl+/`) lists everything below in the app itself. The command palette also lists your sites at the bottom (grouped by domain): **Open** a site in the browser, or **Secure / Unsecure** it (toggle HTTPS), without leaving the keyboard. | Action | macOS | Linux | What it does | |---|---|---|---| | Command palette | `⌘K` | `Ctrl+K` | Search-and-run overlay for every page and action | | Shortcuts | `⌘/` | `Ctrl+/` | Show this list inside the app | | Go to a page | `⌘1` … `⌘9` | `Ctrl+1` … `Ctrl+9` | Jump straight to a sidebar page (see order below) | | Settings | `⌘,` | `Ctrl+,` | Open the Settings page | | Find | `⌘F` | `Ctrl+F` | Focus the page's filter box (Sites, Dumps) | | New | `⌘N` | `Ctrl+N` | Start the page's primary action (Add site, Install PHP) | | Refresh | `⌘R` | `Ctrl+R` | Re-fetch the current page's data | | Restart daemon | `⇧⌘R` | `Ctrl+Shift+R` | Restart `yerdd` | | Toggle theme | `⇧⌘L` | `Ctrl+Shift+L` | Switch light / dark (applies to every window) | | Open Mail viewer | `⇧⌘M` | `Ctrl+Shift+M` | Open the standalone Mail capture window | | Open Dumps viewer | `⇧⌘D` | `Ctrl+Shift+D` | Open the standalone Dumps telemetry window | | Link Site | `⇧⌘N` | `Ctrl+Shift+N` | Open the Link-site dialog on the Sites page | | Park Folder | `⇧⌘P` | `Ctrl+Shift+P` | Open the park-folder picker on the Sites page | | Cycle Dumps tabs | `⌃⇥` / `⌃⇧⇥` | `Ctrl+Tab` / `Ctrl+Shift+Tab` | Move between categories in the Dumps viewer | | Close window | `⌘W` | `Ctrl+W` | Hide the window to the tray | | Close dialog | `Esc` | `Esc` | Dismiss the open modal | `⌘1`…`⌘9` follow the sidebar order: **1** Overview, **2** PHP, **3** Sites, **4** Tooling, **5** Services, **6** Mail, **7** Dumps, **8** Settings, **9** Doctor. ::: info Quitting the app There's no Quit shortcut: closing the window (`⌘W` / `Ctrl+W`) hides it to the tray and leaves the daemon running, by design. Quit from the tray menu, or on macOS with the standard `⌘Q`. ::: ## Related * [Getting Started](./getting-started) - install Yerd and walk through the first-run onboarding journey * [The Daemon](./daemon) - what `yerdd` is and how it runs * [Sites](./sites) · [PHP Versions](./php-versions) · [HTTPS & Certificates](./https) - the features the GUI surfaces * [Elevation & Privileges](./elevation) - how "Fix" actions stay root-free * [CLI Reference](../reference/cli/) - the `yerd` command line, a first-class alternative * [Desktop App Internals](../developer/gui) - the Tauri/Vue architecture for contributors * [Source on GitHub](https://github.com/forjedio/yerd) - `apps/yerd-gui` --- --- url: 'https://yerd.app/guide/sites.md' --- # Sites A **site** is a target Yerd serves on the `.test` TLD. Each one has a name, a document root, a **served web root** (auto-detected per framework), a PHP version, and an HTTPS flag. The daemon keeps a registry and resolves every request's `Host:` header to exactly one site. You register sites two ways: * **Parking** points Yerd at a *parent* directory, and every child folder becomes a site (`.test`). Good for a `~/Sites` workspace you add to often. * **Linking** registers *one* directory under a name you choose. Good for a project outside your parked workspace, or when the site name shouldn't match the folder name. Both route identically; only the registration differs. ## In the desktop app The **Sites** page (under **Environment** in the sidebar) is the home base for managing your sites. It lists every `.test` site as a scannable card you can act on, with the registration controls in the header. Most day-to-day site work happens here without touching the terminal. * Each card is a site you can click to open in your browser (at its primary domain), with badges for `parked`/`linked`, PHP version, HTTPS/HTTP, a `+N` badge when the site has extra domains, and an amber notice if another site has claimed its apex. * A card's **Edit…** dialog (from its `⋯` menu) covers PHP version, [web root](#web-root-the-served-directory), HTTPS, and [group](#site-groups) in one place - no commands. The same `⋯` menu has **Manage domains…** for setting the primary domain and adding/removing aliases and wildcards. * **Park folder** and **Link site** in the header register new sites: Park folder opens a directory picker, Link site opens a modal to name a single directory. * A separate **Parked folders** section lists each parked root with a count of the sites it produces, plus Reveal folder and Un-park. For the full tour of the app, see [Features](./desktop-app#sites). ### Site groups Sites can be organised into named groups - a GUI-only, cosmetic layer for scanning a large site list (client work, personal projects, whatever grouping makes sense to you). Groups don't affect routing, PHP, or HTTPS; the CLI has no concept of them. * **Create a group** from the header's **⋯** menu → **New group…**. The page still shows the classic flat grid until at least one group exists; once it does, sites render as collapsible group sections instead. * **Assign a site to a group** from its **Edit…** dialog: a **Group** field lists the groups you've created plus **No group**. A site only shows up in a group once you've set this. * **Reorder groups** with the up/down arrows next to a group's name (shown on hover). * **Rename or delete a group** from the pencil icon next to it, which opens one **Edit group** dialog: change the name and **Save**, or click **Delete group** for a second confirmation step naming the group. Deleting a group doesn't remove its sites - they fall back to Unallocated. * Each group is a **collapsible section** with a site count badge; collapsed/expanded state is remembered per group. * Sites with no group assigned - or whose assigned group was deleted - appear in a synthetic **Unallocated** section at the end, with no management controls of its own. Group names are unique case-insensitively (like site names) and can't be `Unallocated`, which is reserved for the synthetic bucket. ## Create a new Laravel site Beyond registering folders you already have, the app can **scaffold a brand-new Laravel project** for you. Open the **Create** menu in the Sites header and choose **New Laravel site** to launch a short, four-step wizard - **Basics → Stack → Testing → Review** - that runs `laravel new` under the PHP version you pick and registers the result as a `.test` site automatically. ::: tip Prerequisites Creating a Laravel site needs a PHP version, **Composer**, and the **Laravel installer**. If any are missing, the wizard offers to install them first; it can also use ones you already have [installed externally](./tooling#external-tools) (e.g. a global Composer). Starter kits that need Node or Bun pull the runtime in during the build. ::: ### Basics * **Project name** - the site is served at `.test`. * **Location** - pick the parent folder. If it's a [parked](#parking-a-directory) root the new site is served automatically; any other folder is [linked](#linking-a-directory) under the project name. * **PHP version** - the version the site (and the installer) runs on. * **HTTPS** - serve it over TLS from day one (you can toggle this later too). ### Stack Choose a **starter kit**: **None** (a plain skeleton, no auth scaffolding), the official **React**, **Vue**, or **Svelte** kits (Inertia + TypeScript), **Livewire** (Blade + PHP), or **Community…** to scaffold from any `--using `. ### Testing * **Testing framework** - **Pest** or **PHPUnit**. * **Database** - SQLite, MySQL, MariaDB, PostgreSQL, or SQL Server. * **Initialise git** - run `git init` in the new project. * **Laravel Boost** - install [Boost](https://laravel.com/docs) for AI-assisted coding. ### Review A final summary of your choices - site name, path, PHP version, and HTTPS. Click **Create** and the dialog switches to a live progress view (**Preflight → Scaffolding → Registering → Done**) streaming the installer's output, so you can watch the scaffold and dependency install happen. When it finishes, the project is on disk, registered (parked or linked), served at `.test`, and ready to open in your browser or reveal in your file manager - no extra steps. ## Create a new WordPress site The same **Create** menu can scaffold a brand-new WordPress install for you. Choose **New WordPress site** to launch a four-step wizard - **Basics → WordPress → Database → Review** - that provisions a database, runs WP-CLI's `core download`/`config create`/`core install`, sets pretty permalinks, and registers the result as a `.test` site automatically. ::: tip Prerequisites Creating a WordPress site needs a PHP version and **WP-CLI**, and the wizard offers to install whatever's missing. WP-CLI must be Yerd's own build - Yerd runs it directly rather than through a `wp` on your `PATH`, so an [externally installed](./tooling#external-tools) `wp` doesn't count and your own copy is left alone. Yerd's **Composer** builds WP-CLI, so it's offered as a prerequisite too when WP-CLI still needs installing - see [Tooling](./tooling). ::: ### Basics * **Project name** - the site is served at `.test`. * **Location** - pick the parent folder. If it's a [parked](#parking-a-directory) root the new site is served automatically; any other folder is [linked](#linking-a-directory) under the project name. * **PHP version** - the version the site (and WP-CLI) runs on. * **HTTPS** - serve it over TLS from day one (you can toggle this later too). ### WordPress * **Core version** - a specific WordPress release, or **Latest**. * **Locale** - the install language (e.g. `en_US`, `en_GB`). * **Site title** - WordPress's own site name, set at install time. * **Admin username / email / password** - the first administrator account. **Generate** fills in a random password; the daemon re-validates all three server-side regardless of what the wizard sent. ### Database * **Database engine** - **MySQL** or **MariaDB** (the only two WordPress core itself supports). * **Database name** and **table prefix** - defaults are derived from the project name; both can be edited. Yerd provisions the database as part of creating the site, installing/starting the chosen engine first if it isn't already running - see [Services & Databases](./services). ### Review A final summary of your choices. Click **Create** and the dialog switches to a live progress view streaming each phase - **Preflight → Provisioning database → Downloading WordPress → Configuring → Installing → Registering → Done**. When it finishes, the site is on disk, registered, served at `.test`, and ready to use - **Open folder**, **Open in browser**, or **WP Admin** to sign in as the administrator you just created (see below). ## WordPress one-click admin login A WordPress site created through the wizard has **one-click admin login** turned on by default: opening **WP Admin** signs you in as the site's administrator instead of showing WordPress's own login screen. Existing or parked WordPress sites can opt in the same way. * A WordPress site's card shows a **WP Admin** action in its `⋯` menu, plus a **WPA** badge when one-click login is on - both open the site's `/wp-admin/` pre-authenticated. * Turn it on or off, and choose **who** to sign in as, from the site's **Edit…** dialog: the **WordPress Auto Admin Login** toggle and a **Sign in as** picker (defaults to the earliest-created administrator). ::: info How it works Opening **WP Admin** mints a short-lived, single-use login token and appends it to the `/wp-admin/` URL. The proxy recognises and consumes the token on the first request, signing you in before redirecting - it's never valid a second time, and it does nothing outside that one request. If the resolver is off ([Localhost Access](./localhost-access)) or minting fails for any reason, **WP Admin** falls back to WordPress's ordinary login screen instead. ::: ## From the command line Everything the Sites page does maps to a `yerd` command. These are the same operations against the same daemon, so anything you do here shows up in the app immediately. ### Parking a directory `yerd park ` registers a directory as a **parked root**. Each immediate child directory becomes a site named after the folder: ```sh yerd park ~/Sites # ~/Sites/blog -> http://blog.test # ~/Sites/shop -> http://shop.test # ~/Sites/my-app -> http://my-app.test ``` Add a folder and its site is live; delete it and the site disappears. The child folder is the document root. ::: tip You can park multiple roots. They all contribute children to one flat namespace of `.test` names. ::: To stop serving a parked root, un-park it. This removes only the parked root, not any linked sites: ```sh yerd unpark ~/Sites ``` Un-parking matches the stored path exactly. List the parked roots first if you're unsure what was registered: ```sh yerd list parked ``` ::: info `yerd list parked` shows every parked root, including empty ones. An empty root produces no sites, so it won't appear in `yerd sites`, but it's still parked. ::: ### Linking a directory `yerd link ` registers a single directory as a named site. The name becomes `.test`; the directory is its document root: ```sh yerd link my-app ~/code/my-app # -> http://my-app.test ``` Name and directory are both optional shorthand for the current directory: ```sh cd ~/code/my-app yerd link # links the cwd, named "my-app" after its folder yerd link my-app # same, with an explicit name yerd link ../other-app # links a relative path, named "other-app" after its folder ``` A single positional argument is treated as a directory (and the name derived from its folder) when it contains a path separator or is `.`/`..`; otherwise it's treated as a bare name and the current directory is linked. Web-root detection (`public/` for Laravel, etc. - see [Web root](#web-root-the-served-directory)) runs automatically the first time a site is linked, so a Laravel app's `SERVED` directory is usually already correct with no extra `yerd root` step. To remove it, unlink by name: ```sh yerd unlink my-app ``` ### Site name rules A site name is a single DNS label, validated and lowercased before it reaches the daemon (a bad name fails as a usage error, no connection made): * ASCII letters, digits, and hyphen only (`[a-z0-9-]`). * No dots; a name is one label, not a domain. * No leading or trailing hyphen. * 1-63 characters. * Case-insensitive: `My-App` is stored and served as `my-app`. Valid: `my-app`, `api2`, `wp-site`. Invalid: `my.app`, `my_app`, `-app`, `app-`. ::: warning Names are unique. Since they're lowercased first, `Foo` and `foo` collide, so the second registration is a duplicate. ::: ### Listing your sites `yerd sites` lists every site (parked and linked) with its kind, PHP version, secure flag, served subdirectory, and document root: ```sh yerd sites ``` ``` NAME KIND PHP SECURE SERVED DOCROOT blog parked 8.5 false public /Users/you/Sites/blog my-app linked 8.3 true / /Users/you/code/my-app shop parked 8.5 false public /Users/you/Sites/shop ``` The `SERVED` column is the web root relative to the document root; `/` means the project root itself is served. Sites print in name order; an empty registry prints `no sites`. Add `--json` for machine-readable output: ```sh yerd sites --json ``` ### Command reference | Command | What it does | |---|---| | `yerd park ` | Park a directory; each child folder is served at `.test`. | | `yerd unpark ` | Un-park a directory. Linked sites are untouched. | | `yerd link [name] [dir]` | Serve a directory as a named site; both args are optional shorthand for the current directory. | | `yerd unlink ` | Remove a site by name. | | `yerd sites` | List every site (name, kind, PHP, secure, served path, doc-root). | | `yerd list parked` | List parked roots, including empty ones. | | `yerd secure ` / `yerd unsecure ` | Turn HTTPS on / off for a site. | | `yerd root ` | Set the served directory (web root) for a site. | | `yerd root --auto` | Reset a site to automatic web-root detection. | | `yerd domain ` | Manage a site's domains (primary, aliases, subdomains, wildcards). See [Domains](../reference/cli/domains). | For per-site PHP, see [PHP Versions](./php-versions). For the full command surface, see the [CLI Reference](../reference/cli/sites). ## How routing works Yerd normalises the `Host:` header and resolves it to a site using the rules below. ### The `.test` TLD By default Yerd serves on `.test`, a reserved TLD that's safe for local development. A host only resolves if it ends in the configured TLD: ``` blog.test -> site "blog" blog.example -> no match (wrong TLD) blog.notthetest -> no match (suffix collision doesn't count) ``` The bare TLD (`test`, or `test.`) has no site label and never resolves. ::: info The TLD is configurable (for example `dev.local`); the default is `.test`, and `yerd status` shows the active one. See [DNS & .test Domains](./dns) for how `*.test` requests reach the daemon. ::: ### Host cleanup Matching is case-insensitive and tolerant of cosmetic bits clients send. Before matching, Yerd: * Lowercases the host (`FOO.TEST` matches `foo`). * Strips a port (`foo.test:8443` becomes `foo.test`; a trailing `:` is fine). * Strips one trailing FQDN dot (`foo.test.` becomes `foo.test`). Hosts that can't be a `.test` name never match: IPv6 literals (`[::1]`), non-ASCII (`föö.test`), an empty host, a leading dot, or a malformed port (`foo.test:abc`). ### Domains, subdomains, and wildcards By default a site answers for **exactly one** host: its apex `.test`. A site can hold more than one domain, and can answer subdomains and wildcards, but each is **explicit** - you register it with [`yerd domain`](../reference/cli/domains). After confirming the host ends in `.test`, Yerd resolves it with one exact lookup, then one single-label wildcard lookup: ```text foo.test -> foo (exact apex) corp.test -> foo (only if `corp` was added to foo) api.foo.test -> no match (404 by default; subdomains are not implicit) api.foo.test -> foo (once `*.foo.test` is added to foo) x.api.foo.test -> no match (a wildcard matches one label; needs `*.api.foo.test`) ``` **This changed in v2.** Earlier builds made every subdomain fall through to its parent site implicitly. That catch-all is gone: `api.foo.test` is a 404 until you register it. Re-enable the old behavior for a site with `yerd domain add foo '*.foo.test'`. The upside is that `foo.test` and `*.foo.test` can now be **two different sites**. **Exact beats wildcard.** The exact lookup runs first, so a registered `api.foo.test` (exact) wins over a `*.foo.test` wildcard on another site. A wildcard is never a site's primary (canonical) domain - only exact domains can be primary. Manage a site's domains from the CLI: ```sh yerd domain list blog # show blog's domains, primary marked yerd domain add blog corp.test # add an alias yerd domain add blog '*.blog.test' # add a wildcard (quote it for the shell) yerd domain primary blog corp.test # make corp.test the canonical address yerd domain remove blog blog.test # drop a domain (a site keeps >=1 exact) yerd domain reset blog # back to the default apex only ``` For a WordPress site, changing the primary domain also rewrites its `siteurl`/`home`. In the desktop app the same lives under a site's **⋯ → Manage domains…**. See the [domains reference](../reference/cli/domains) for the full command surface and rules. ### Document roots and the served web root The **document root** is the project directory a site maps to: the child folder for a parked site, or the path you passed to `yerd link`. It's shown in `yerd sites`. The directory actually served to the browser is the document root's **web root** - which, for most modern frameworks, is a subdirectory rather than the project root itself. Yerd detects this automatically (see [Web root](#web-root-the-served-directory) below), so a Laravel app parked at `~/Sites/blog` is served from `~/Sites/blog/public` without any configuration. ### The secure (HTTPS) flag Sites start insecure (HTTP only). Securing one serves it over HTTPS with a certificate from Yerd's local CA: ```sh yerd secure my-app # serve over HTTPS yerd unsecure my-app # back to HTTP only ``` ::: tip `yerd secure` promotes a parked site to a tracked (linked) entry so the flag has somewhere to live, then flips it. See [HTTPS & Certificates](./https) for how the CA and per-site certificates work. ::: ## Web root (the served directory) Most PHP frameworks don't serve from the project root - they put a front controller in a subdirectory and keep application code out of the document root. Yerd detects the right directory automatically and serves it, so you don't hand-configure a web server: | Framework | Served from | |---|---| | Laravel, Symfony (4+), CodeIgniter 4 | `public/` | | CakePHP | `webroot/` | | Drupal (Composer), Yii2 | `web/` | | Magento 2 | `pub/` | | WordPress, plain PHP | the project root | Detection runs in the daemon when a site is registered and whenever its project changes - it reads `composer.json`, looks for framework marker files (`artisan`, `wp-config.php`, `bin/console`, …), and probes for a front controller (`index.php`) in the conventional subdirectories. A site with nothing to detect yet (an empty folder) serves the project root for now, and Yerd watches it so that **cloning a project into a parked folder makes it serve from the right directory within a second or so - no restart, no refresh**. The served path shows up in `yerd sites` (the `SERVED` column, `/` meaning the project root itself). ::: info Static files are served directly A request that resolves to a real file under the served root (a stylesheet, image, `favicon.ico`, compiled JS, …) is returned straight from disk by the proxy, with a guessed `Content-Type` - it never touches PHP. A directory request (including the site root) falls back to `index.html` or `index.htm` from that directory when there's no `index.php` there, so a plain static site (no PHP at all) works with no extra configuration. Everything else is handed to the framework's front controller (`index.php`). PHP source files are never served as static bytes. A symlink is allowed to point anywhere inside the site's project directory - so Laravel's `public/storage -> ../storage/app/public` link works with no extra setup - but a symlink that escapes the project directory entirely is refused with an explicit `403 Forbidden` naming the requested path, rather than being silently handed to PHP. ::: ### Overriding the served path When detection guesses wrong, or you have an unconventional layout, set the served directory explicitly: ```sh yerd root my-app public # serve my-app.test from /public yerd root my-app web/app # a nested directory is fine yerd root my-app --auto # forget the override; go back to auto-detection ``` `yerd root ` with no path also resets to auto-detection. The path is relative to the site's directory (an absolute path inside it works too); Yerd validates that it resolves to a directory **inside** the project and rejects anything that escapes it. A manual override always wins and is never overwritten by re-detection. ::: tip In the desktop app The [Sites view](./desktop-app#sites) shows the served web root as a badge per site, and its **Edit…** dialog sets it directly - leave the field blank to go back to auto-detection. ::: ## Related * [PHP Versions](./php-versions) - set the global default and pin a site to a version. * [HTTPS & Certificates](./https) - the local CA and the `secure` flag. * [DNS & .test Domains](./dns) - how `*.test` requests reach the daemon. * [Configuration Reference](../reference/configuration) - where sites and the TLD are stored. --- --- url: 'https://yerd.app/guide/proxies.md' --- # Reverse Proxies Yerd can put a `.test` address in front of a service it doesn't run itself - a Reverb server, a Node or Vite dev server, a Docker container, anything already listening on a port. The service gets Yerd's DNS, its trusted HTTPS, and a clean `.test` hostname, with no extra config on the service's side. There are two shapes, and you'll usually want the second for Laravel work: * A **whole-host proxy** gives a service its own hostname: `reverb.test` → a service on `localhost:8080`. * A **path rule** routes one path *on an existing site* to a service: `myapp.test/app` → a service, while every other path on `myapp.test` is still served by PHP. This keeps everything **same-origin**, which is exactly what Laravel Reverb needs (`wss://myapp.test/app`) so cookies and CORS work without a second domain. Unlike [Herd](https://herd.laravel.com), which writes an nginx vhost, Yerd's proxy is built into its own request path - there's nothing to configure and no web server to reload. ## Whole-host proxies Point a new `.test` host at a running service: ```sh yerd proxy add reverb http://localhost:8080 ``` Now `http://reverb.test/` reaches the service. Serve it over HTTPS the same way you would a site - a proxy is secured on its own name: ```sh yerd secure reverb # https://reverb.test/ (trusted cert, HTTP redirects to HTTPS) ``` Remove it with `yerd proxy remove reverb`. ## Path rules (the Reverb case) Attach a path to an existing site. Say `myapp` is a Laravel app and Reverb is running on `:8080`: ```sh yerd proxy add myapp /app http://127.0.0.1:8080 ``` * `https://myapp.test/` and everything else → **PHP** (Laravel), unchanged. * `https://myapp.test/app` (and `/app/...`) → **Reverb**, websockets included. The rule inherits the site's TLS, so once `myapp` is secured the `/app` path is too - your JS client connects to `wss://myapp.test/app` on the same origin. The full path is passed through to the upstream unchanged (`/app/...` reaches Reverb as `/app/...`), which is what Reverb expects. Remove a rule with `yerd proxy remove myapp /app`. ## Upstreams and headers The upstream is `http://host:port` or `https://host:port`. For an `https://` upstream, Yerd verifies the certificate for a genuine public host but **skips verification for a local host** (`localhost`, a loopback/private IP, or a `.test` name) - self-signed dev backends are the norm there. Yerd preserves the original `Host` header (many upstreams key vhosts on it) and adds `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Real-IP`. Websocket upgrades are tunnelled through. If the upstream is down, a request returns **`502 Bad Gateway`** rather than hanging. ::: warning You can't proxy to Yerd itself A target that points back into Yerd - a `.test` host, or `localhost` on the port Yerd's own proxy is listening on - is rejected, because it would loop forever. Point the target at the service's real port instead. In rootless mode Yerd binds `8080`/`8443`, so a dev server on one of those ports will need to move. ::: ## Listing what's configured ```sh yerd proxy list ``` shows every whole-host proxy and every per-site path rule. `yerd --json proxy list` gives the same data as JSON for scripting or the desktop app. For the full command surface, flags, and validation rules, see the [Proxies CLI reference](../reference/cli/proxies). --- --- url: 'https://yerd.app/guide/php-versions.md' --- # PHP Versions Yerd runs **any number of PHP versions side by side** and lets you pick which one each site uses. PHP isn't bundled, so the install stays small. The first time you ask for a version, Yerd downloads a prebuilt, statically-linked PHP build that Yerd publishes itself (signed and checksummed) and supervises one PHP-FPM pool per version behind the [reverse proxy](./sites). ## In the desktop app The fastest way to manage PHP is the **PHP** page (under the **Environment** group) in the [desktop app](./desktop-app#php). It's a live view of every installed version and the controls to change them, with no commands to remember. * A table of installed versions shows live FPM pool state, patch level, pool memory (RSS), and whether an update is available. * **Install** opens a picker of installable versions (already-installed ones are hidden); progress streams live next to the Install button as the prebuilt static build downloads. An **Install a legacy version** toggle (off by default) swaps the picker over to the [legacy minors](#legacy-php-versions) (7.4 / 8.0 / 8.1) behind a warning block and a mandatory confirmation checkbox; when every current version is already installed the toggle starts on and locks there, since legacy is all that's left to add. * **Refresh** re-checks for updates and **Update all** updates every version with one pending - [updates are notify-only](#updates-are-notify-only). * Each row's `⋯` menu offers **Restart**, **Set default** (marks it with a star; disabled for legacy rows, which are tagged with a `legacy` badge), **Update** (when available), and **Uninstall**; **Restart all** restarts every running pool. * A **Default settings** card edits the [global ini defaults](#tuning-php-settings) applied to every version; leave a field blank to use PHP's built-in default, and saving restarts running pools to apply. * A **Per-version configuration** card lists your versions down the side, newest first; picking one shows everything scoped to it: the settings form (empty fields inherit the defaults; see [Per-version configuration](#per-version-configuration)), its [custom extensions](#custom-extensions), and a free-form ini-directive editor (e.g. `xdebug.mode = debug`). Each row badges how much that version has configured and marks unsaved edits, so switching versions never loses work. Saving restarts only that version's pool. ## From the command line ### Installing a version ```sh yerd install php 8.5 ``` Yerd detects your platform (`linux`/`macos`, `x86_64`/`aarch64`), fetches Yerd's signed `php.json` manifest, resolves the single published build for your platform and minor, downloads the CLI and FPM tarballs, verifies each against its published SHA-256, then atomically swaps them into place. The manifest is the source of truth for what's installable, so a new PHP patch becomes available as soon as Yerd's build pipeline publishes it. Installs are **idempotent**: running it again replaces the directory with a fresh download of the current build. If the version isn't published for your platform, the install fails cleanly and writes nothing. The running daemon picks up a new version automatically, no restart required. ::: info A version is always a major.minor A "PHP version" means a `major.minor` pair like `8.5`, never a full patch like `8.3.12`. Yerd installs and tracks the latest patch of the minor you ask for, and updates move you to a newer patch of that same minor. Input is `8.5` (or `php8.5`); major must be `5..=9`, minor `0..=99`. ::: ::: info Downloads are signed and hash-verified The `php.json` manifest is signed with a dedicated minisign key whose public half is embedded in Yerd; the daemon verifies that signature before trusting the manifest, then verifies each downloaded tarball against the SHA-256 the manifest lists. Because PHP runs as you, this verification is on the install critical path, not just updates. ::: ### Legacy PHP versions Yerd also serves three **out-of-support** minors - **7.4**, **8.0**, and **8.1** - from a separate, independently-signed `php-legacy.json` manifest. It's the same minisign key and the same per-tarball SHA-256 verification as `php.json`, just a different listing. These versions are past their upstream end of life and may contain **unpatched security vulnerabilities**, so installing one requires an explicit opt-in: ```sh yerd install php 7.4 --legacy ``` Running `yerd install php 7.4` without `--legacy` refuses and prints an out-of-support warning instead of installing. In the desktop app, the Install picker's **Install a legacy version** toggle replaces the version list with the legacy minors and opens a warning block and a mandatory confirmation checkbox ("I understand and want to install this legacy version anyway.") before the Install button is enabled. A legacy version carries hard restrictions once installed: * **Cannot be the global default.** `yerd use 7.4` is refused; the desktop app disables **Set default** for legacy rows. * **No code coverage.** `phpcover`, `php7.4cover` / `php8.0cover` / `php8.1cover`, and `yerd coverage` all error rather than run - see [Code Coverage](./code-coverage). * **No yerd-dumps capture.** The Dumps view flags legacy rows as unsupported - see [Laravel Dumps](./laravel-dumps). * **No `pcov` or `yerd-dump` `.so` builds.** Neither extension is built for EOL PHP, which is why coverage and dumps don't work on legacy versions. A legacy version **can** still be assigned to an individual site (`yerd use my-app 7.4`), just not as the global default. See [Per-site versions](#per-site-versions). ### Bundled extensions Yerd's builds ship the **bulk** extension set, so a real-world Laravel app has what it needs out of the box - highlights include **`intl`** (ICU, required by Laravel's `Number` helper), **`sodium`**, **`mysqli`**, **`gd`**, **`imagick`**, **`redis`**, **`opcache`**, and **`swoole`**. Database access is covered by the **`pdo_mysql`**, **`pdo_pgsql`**, and **`pdo_sqlite`** PDO drivers (so `PDO::getAvailableDrivers()` returns all three) alongside the native `mysqli`, `pgsql`, and `sqlite3` extensions. Coverage is provided separately by `pcov` - see [Code Coverage](./code-coverage). The authoritative list for any install is `php -m` (via the [`php` shim](#the-global-default)); the full set for the current builds is below. It rarely changes between patch updates. Entries marked *(core)* are part of every standard PHP build; the rest are the extras Yerd's "bulk" build adds. Two extensions are **new in PHP 8.5** and absent on 8.4, as noted. | Extension | What it does | | --- | --- | | `apcu` | In-process shared-memory cache (APCu) for storing user data across a pool's requests. | | `bcmath` | Arbitrary-precision decimal arithmetic for money and other exact-math needs. | | `bz2` | bzip2 stream compression and decompression. | | `calendar` | Conversions between calendar systems (Julian day count, Gregorian, Jewish, French). | | `Core` | *(core)* The PHP engine itself: language constructs and built-in functions. | | `ctype` | *(core)* Fast character-class checks such as `ctype_digit()` and `ctype_alpha()`. | | `curl` | Network transfers via libcurl (HTTP/S, FTP, and more); the default backend for Guzzle. | | `date` | *(core)* Date and time handling, including `DateTime` and timezone data. | | `dba` | Key/value database abstraction over dbm-style engines (GDBM and friends). | | `dom` | *(core)* Tree-based DOM API for reading and manipulating XML and HTML documents. | | `event` | libevent bindings for event-driven, non-blocking I/O loops. | | `exif` | Reads EXIF metadata (camera, orientation, GPS) embedded in image files. | | `fileinfo` | *(core)* Detects a file's MIME type from its contents rather than its name. | | `filter` | *(core)* Validates and sanitizes data with `filter_var()` (emails, URLs, ints, …). | | `ftp` | Client-side FTP protocol support. | | `gd` | Image creation and manipulation: resize, crop, draw, and convert common formats. | | `gmp` | Arbitrary-precision integer arithmetic via GNU MP, faster than `bcmath` for big integers. | | `hash` | *(core)* General hashing framework (`hash()`, HMAC) covering many algorithms. | | `iconv` | *(core)* Character-set conversion between text encodings. | | `imagick` | ImageMagick bindings for advanced image processing and a wide range of formats. | | `imap` | Access to IMAP, POP3, and NNTP mailboxes. | | `intl` | Unicode/ICU internationalization: number and date formatting, collation, transliteration - required by Laravel's `Number` helper. | | `json` | *(core)* JSON encoding and decoding. | | `lexbor` | **New in PHP 8.5.** The Lexbor HTML5 engine powering the new `\Dom\HTMLDocument` parser. | | `libxml` | *(core)* The shared libxml2 foundation the other XML extensions build on. | | `mbstring` | Multibyte-safe string functions for UTF-8 and other encodings. | | `mysqli` | The improved, feature-complete MySQL/MariaDB driver. | | `mysqlnd` | The native driver backend that `mysqli` and PDO's MySQL driver run on. | | `openssl` | TLS, symmetric/asymmetric encryption, signatures, and X.509 certificate handling. | | `opentelemetry` | Engine hooks that let OpenTelemetry auto-instrument code for tracing and metrics. | | `pcntl` | Unix process control (fork, signals, `waitpid`) for CLI worker processes. | | `pcre` | *(core)* Perl-compatible regular expressions, i.e. the `preg_*` functions. | | `PDO` | *(core)* The database-access abstraction layer; the bundled drivers cover MySQL, PostgreSQL, and SQLite. | | `pdo_mysql` | PDO driver for MySQL/MariaDB. | | `pdo_pgsql` | PDO driver for PostgreSQL. | | `pdo_sqlite` | PDO driver for SQLite. | | `pgsql` | Native PostgreSQL client library (libpq-backed `pg_*` functions). | | `Phar` | *(core)* PHP Archive support: bundle a whole application into one distributable file. | | `posix` | POSIX system-call bindings (users, groups, process info) on Unix. | | `protobuf` | Google Protocol Buffers runtime for compact, fast binary (de)serialization. | | `random` | *(core)* The modern randomness API (`Randomizer` engines, `random_int()`). | | `readline` | Interactive line editing and history for CLI and REPL programs. | | `redis` | Client for the Redis / Valkey in-memory data store (phpredis). | | `Reflection` | *(core)* Runtime introspection of classes, functions, and attributes. | | `session` | *(core)* Server-side session state management. | | `shmop` | Direct read/write access to shared-memory segments. | | `SimpleXML` | *(core)* Simple object-oriented access to XML documents. | | `soap` | SOAP client and server for XML web services. | | `sockets` | Low-level BSD sockets API for building custom network protocols. | | `sodium` | *(core)* Modern libsodium cryptography: authenticated encryption, signing, and hashing. | | `SPL` | *(core)* Standard PHP Library: data-structure classes, iterators, and interfaces. | | `sqlite3` | The self-contained, embedded SQLite database engine. | | `standard` | *(core)* PHP's standard function library (strings, arrays, math, files, URLs, …). | | `swoole` | Coroutine-based async runtime and high-performance server framework. | | `sysvmsg` | System V message-queue inter-process communication. | | `sysvsem` | System V semaphores for coordinating processes. | | `sysvshm` | System V shared-memory inter-process communication. | | `tokenizer` | *(core)* Tokenizes PHP source code; used by linters and static analysis tools. | | `uri` | **New in PHP 8.5.** A built-in, spec-compliant URI parser (RFC 3986 and WHATWG). | | `xml` | *(core)* Event-based (SAX/Expat) XML parsing. | | `xmlreader` | *(core)* Pull-based streaming reader for large XML documents. | | `xmlwriter` | *(core)* Streaming writer for generating XML. | | `xsl` | XSLT 1.0 stylesheet transformations over the DOM. | | `Zend OPcache` | Caches compiled PHP bytecode in shared memory so scripts aren't re-parsed each request (a Zend extension). | | `zip` | Reading and writing ZIP archives. | | `zlib` | gzip / deflate stream compression. | Need something not in this set? Register your own with [`yerd php ext`](#custom-extensions). [Legacy versions](#legacy-php-versions) (7.4 / 8.0 / 8.1) ship a smaller, uniform extension set built once across all three EOL minors, dropping a few extensions that need PHP 8.0+ or a newer `swoole` than 7.4 can run. | Extension | What it does | | --- | --- | | `apcu` | In-process shared-memory cache (APCu) for storing user data across a pool's requests. | | `bcmath` | Arbitrary-precision decimal arithmetic for money and other exact-math needs. | | `bz2` | bzip2 stream compression and decompression. | | `calendar` | Conversions between calendar systems (Julian day count, Gregorian, Jewish, French). | | `ctype` | Fast character-class checks such as `ctype_digit()` and `ctype_alpha()`. | | `curl` | Network transfers via libcurl (HTTP/S, FTP, and more); the default backend for Guzzle. | | `dba` | Key/value database abstraction over dbm-style engines (GDBM and friends). | | `dom` | Tree-based DOM API for reading and manipulating XML and HTML documents. | | `event` | libevent bindings for event-driven, non-blocking I/O loops. | | `exif` | Reads EXIF metadata (camera, orientation, GPS) embedded in image files. | | `fileinfo` | Detects a file's MIME type from its contents rather than its name. | | `filter` | Validates and sanitizes data with `filter_var()` (emails, URLs, ints, …). | | `ftp` | Client-side FTP protocol support. | | `gd` | Image creation and manipulation: resize, crop, draw, and convert common formats. | | `gmp` | Arbitrary-precision integer arithmetic via GNU MP, faster than `bcmath` for big integers. | | `iconv` | Character-set conversion between text encodings. | | `imagick` | ImageMagick bindings for advanced image processing and a wide range of formats. | | `imap` | Access to IMAP, POP3, and NNTP mailboxes. | | `intl` | Unicode/ICU internationalization: number and date formatting, collation, transliteration. | | `mbregex` | Multibyte-aware regular expressions (the `mb_ereg*` functions). | | `mbstring` | Multibyte-safe string functions for UTF-8 and other encodings. | | `mysqli` | The improved, feature-complete MySQL/MariaDB driver. | | `mysqlnd` | The native driver backend that `mysqli` and PDO's MySQL driver run on. | | `opcache` | Caches compiled PHP bytecode in shared memory so scripts aren't re-parsed each request. | | `openssl` | TLS, symmetric/asymmetric encryption, signatures, and X.509 certificate handling. | | `pcntl` | Unix process control (fork, signals, `waitpid`) for CLI worker processes. | | `pdo` | The database-access abstraction layer. | | `pdo_mysql` | PDO driver for MySQL/MariaDB. | | `pdo_pgsql` | PDO driver for PostgreSQL. | | `pdo_sqlite` | PDO driver for SQLite. | | `pgsql` | Native PostgreSQL client library (libpq-backed `pg_*` functions). | | `phar` | PHP Archive support: bundle a whole application into one distributable file. | | `posix` | POSIX system-call bindings (users, groups, process info) on Unix. | | `protobuf` | Google Protocol Buffers runtime for compact, fast binary (de)serialization. | | `readline` | Interactive line editing and history for CLI and REPL programs. | | `redis` | Client for the Redis / Valkey in-memory data store (phpredis). | | `session` | Server-side session state management. | | `shmop` | Direct read/write access to shared-memory segments. | | `simplexml` | Simple object-oriented access to XML documents. | | `soap` | SOAP client and server for XML web services. | | `sockets` | Low-level BSD sockets API for building custom network protocols. | | `sodium` | Modern libsodium cryptography: authenticated encryption, signing, and hashing. | | `sqlite3` | The self-contained, embedded SQLite database engine. | | `sysvmsg` | System V message-queue inter-process communication. | | `sysvsem` | System V semaphores for coordinating processes. | | `sysvshm` | System V shared-memory inter-process communication. | | `tokenizer` | Tokenizes PHP source code; used by linters and static analysis tools. | | `xml` | Event-based (SAX/Expat) XML parsing. | | `xmlreader` | Pull-based streaming reader for large XML documents. | | `xmlwriter` | Streaming writer for generating XML. | | `xsl` | XSLT 1.0 stylesheet transformations over the DOM. | | `zip` | Reading and writing ZIP archives. | | `zlib` | gzip / deflate stream compression. | Dropped versus the stable builds: * **`opentelemetry`** - requires PHP 8.0+, which breaks the 7.4 build outright. * **`swoole`** - the pinned `swoole` build needs a newer PHP than 7.4 and would fragment across the three EOL minors, so it's dropped to keep one uniform legacy set. * **`swoole-hook-mysql`** - depends on `swoole`. * **`pcov` and `yerd-dump`** - the external `.so` partners from [`forjedio/yerd-php-ext`](https://github.com/forjedio/yerd-php-ext) aren't built for EOL PHP, which is why [coverage](./code-coverage) and [dumps](./laravel-dumps) don't work on legacy versions. ### Custom extensions When you need an extension Yerd's builds don't ship (a PECL module like `scrypt`, or your own compiled `.so`), register it with `yerd php ext`. Yerd loads it into **both** the web (FPM) runtime and the CLI for that version, so `extension_loaded()` returns `true` on a `.test` route and `php -m` lists it - the two used to diverge. ```sh yerd php ext add 8.5 /opt/homebrew/lib/php/pecl/20250925/scrypt.so yerd php ext list yerd php ext remove 8.5 scrypt ``` * **Extensions are tied to a PHP version.** A native `.so` is compiled against one PHP *minor*, so you register it under the version it was built for (`8.5` above); it loads only for that version. * **Every add is load-probed.** Before saving, Yerd actually loads the `.so` into that version's PHP and rejects it if it can't load - a wrong-version build, a missing dependency, or a Zend extension registered without `--zend` fails with a clear message instead of silently breaking your pools. * **Zend extensions** (xdebug/opcache-style) use `--zend`: `yerd php ext add 8.5 /path/xdebug.so --zend`. * **Naming.** The removal handle defaults to the `.so` filename (`scrypt` above); override it with `--name`. * **Missing files** are handled gracefully: if a registered `.so` later disappears (e.g. Homebrew bumps its PECL directory on upgrade), Yerd skips it with a warning rather than failing to start the pool, and `yerd php ext list` marks it `(missing!)`. Adding or removing an extension restarts that version's running FPM pool to apply it. In the desktop app, the same registry lives in the **Extensions** section of the **Per-version configuration** card on the **PHP** page: pick the version's tab, then **Add…** to browse for a `.so`. An extension whose file has gone missing is flagged, and its `⋯` menu can seed a matching ini directive. Registered extensions are stored per version in the config file - see the [Configuration Reference](../reference/configuration#php). ### How versions are stored Each install lands under the per-user data directory: ```text {data}/php/php-8.5/bin/php # the CLI interpreter {data}/php/php-8.5/sbin/php-fpm # the FastCGI process manager {data}/php/php-8.5/.yerd-version # the exact patch installed, e.g. "8.5.6" {data}/bin/php # the default-version CLI shim {data}/bin/php8.5 # a per-version CLI shim ``` The dir is named for the **major.minor** (`php-8.5`); `.yerd-version` records the exact patch (`8.5.6`). Update checks read that marker to decide whether a newer patch exists. The daemon discovers installed versions by walking this directory and finding each `sbin/php-fpm` at startup. ### The global default Yerd has one **global default** version, used for the `php` shim at `{data}/bin/php` and as the fallback for any site that hasn't pinned its own. Set it with one argument: ```sh yerd install php 8.5 yerd use 8.5 ``` A fresh config defaults to **PHP 8.3**, but you'll usually set your own right after installing. ::: warning Legacy versions can't be the default A [legacy version](#legacy-php-versions) (7.4 / 8.0 / 8.1) is ineligible as the global default - `yerd use 7.4` is refused client-side, and the desktop app disables **Set default** for legacy rows. It can still be pinned to an individual site. ::: ::: tip Add the shim dir to your PATH Put `{data}/bin` (Yerd prints the exact path) on your `PATH` so a bare `php` matches the version your sites run. The bare `php` shim resolves the current default at run time, so `yerd use` takes effect immediately with nothing to re-point. ::: Alongside the default `php` shim, Yerd maintains a `php` shim for each installed version (`php8.4`, `php8.3`, ...) so you can reach a specific version directly, plus `phpcover` / `phpcover` shims that run PHP with the pcov coverage driver enabled. See [Code Coverage](./code-coverage). Each shim runs the right PHP with that version's ini and any [custom extensions](#custom-extensions) you've registered. ### Per-site versions Any site can pin its own version. Pass `yerd use` two arguments, a site name and a version: ```sh yerd use my-app 8.3 ``` Now `my-app.test` runs on 8.3 while every other site follows the global default. | Site setting | Effective version | |---|---| | Pinned (`yerd use 8.3`) | `8.3` | | Not pinned | the global default | Clearing a pin reverts the site to whatever the global default is at the time. Check what each site resolves to with `yerd sites`, which lists every site with its kind, PHP version, HTTPS state, and document root. See [Sites](./sites) for parking and linking. ::: warning Pin a version you've installed Pinning a site (or the default) to an uninstalled version means there's no FPM binary to start when a request arrives. Install it first (`yerd install php 8.3`), then pin. `yerd doctor` flags a pool that can't start. ::: ### Listing versions ```sh yerd list php ``` This shows every installed version, marks the default, and flags any with a newer patch available. Update flags come from the **daemon's cache** by default, so no network call is made and the command is instant. | Command | What you get | |---|---| | `yerd list php` | Installed versions, default, cached update flags (no network) | | `yerd list php --check` | Same, but polls the distribution now to refresh update flags | | `yerd list php --available` | Versions installable from the distribution, tagging installed ones | `--available` takes precedence over `--check`. Add `--json` (a global flag) for machine-readable output. ### Updates are notify-only Yerd checks for newer **patches** of the minors you have and tells you about them, but never installs on its own. The daemon periodically polls the distribution, compares each installed minor's latest patch against its `.yerd-version` marker, and on a newer patch logs: ```text a newer PHP patch is available (run `yerd update php`) ``` It records this in the cache `yerd list php` reads. The poll is failure-tolerant: a network or platform failure is logged quietly and your cached state is left untouched. Update on your terms: ```sh yerd update php 8.5 # update just 8.5 to its latest patch yerd update php # update every installed version ``` An update is the same atomic install flow: it moves `8.5.4` → `8.5.6` and never jumps to a different minor. To move minors, run `yerd install php 8.6` and `yerd use 8.6` explicitly. ::: tip Nothing updates behind your back Updates are strictly notify-only. The only automatic network call is the lightweight update check, which downloads nothing but a directory listing. Yerd downloads or swaps a PHP version only when you run `yerd update php`. ::: ### Tuning PHP settings Yerd keeps a small set of **global PHP ini defaults** that are applied to *every* installed version's FPM pool. Set and clear them with `set` / `unset`: ```sh yerd set php memory_limit 512M yerd set php upload_max_filesize 64M yerd unset php memory_limit # reset to PHP's built-in default ``` Only an allowlisted set of directives is accepted (e.g. `memory_limit`, `max_execution_time`, `upload_max_filesize`, `post_max_size`, `display_errors`, `error_reporting`), and the value is validated client-side before it's sent, so a typo is a clean error rather than a broken pool. The configured values are echoed back by `yerd list php` under a `settings:` block. See the [PHP CLI reference](../reference/cli/php#global-php-ini-settings) for the full list and the [Configuration Reference](../reference/configuration#php) for how they're stored and rendered into FPM config. ### Per-version configuration Every setting can also be pinned for a **single** installed version with the `--only` flag - the override wins over the global default for that version only, and applies to both its FPM pool and its CLI: ```sh yerd set php memory_limit 1G --only 8.3 # only PHP 8.3 gets 1G yerd unset php memory_limit --only 8.3 # 8.3 inherits the global value again ``` Beyond the allowlist, `yerd php ini` sets **free-form ini directives** per version - typically the settings of a custom extension. The classic xdebug setup is two commands: ```sh yerd php ext add 8.3 /opt/php/xdebug.so --zend # load the extension yerd php ini set 8.3 xdebug.mode debug # configure it ``` Directive names and values are shape-checked so they can never corrupt the generated config, but Yerd doesn't second-guess their meaning - a directive PHP doesn't recognise is simply ignored by PHP. A per-version change restarts only that version's pool, and per-version configuration survives uninstalling and reinstalling the version. In the desktop app the same lives in the **Per-version configuration** card on the PHP page: pick a version from the list to get the settings form (empty fields inherit the defaults), that version's extensions, and a directive editor. A version that still has extensions registered after being uninstalled stays in the list, so those registrations can be removed. See the [PHP CLI reference](../reference/cli/php#custom-ini-directives) for the rules and the denylist of directives Yerd manages elsewhere. ### Command summary | Command | What it does | |---|---| | `yerd install php ` | Download + install the latest patch of a minor. | | `yerd use ` | Set the global default version (and the `php` shim). | | `yerd use ` | Pin one site to a version. | | `yerd list php [--check]` | List installed versions; `--check` refreshes update flags. | | `yerd list php --available` | List versions installable from the distribution. | | `yerd update php []` | Update one (or all) versions to the latest patch. | | `yerd uninstall php ` | Remove a version's files (blocked if a site uses it). | | `yerd restart php []` | Restart one (or all) running FPM pools. | | `yerd set php [--only ]` | Set a global PHP ini default, or a per-version override with `--only`. | | `yerd unset php [--only ]` | Reset a global setting to PHP's built-in value. With `--only`, remove one version's override so the global value applies again. | | `yerd php ini set ` | Set a free-form ini directive (e.g. `xdebug.mode`) for one version. | | `yerd php ini unset ` | Remove a free-form ini directive. | | `yerd php ini list` | Show per-version overrides and directives. | | `yerd php ext add [--zend] [--name ]` | Register a custom extension (load-probed) for a version. | | `yerd php ext remove ` | Remove a registered extension. | | `yerd php ext list` | List registered custom extensions, grouped by version. | Add `--json` to any command for machine-readable output. ## Related * [Sites](./sites) - parking, linking, and how a request reaches an FPM pool. * [HTTPS & Certificates](./https) - trusted HTTPS per site. * [Diagnostics](./diagnostics) - `yerd status` and `yerd doctor` for when a pool won't start. * [CLI Reference](../reference/cli/) - every command and flag. * [Configuration Reference](../reference/configuration) - where the default and per-site pins live on disk. * [yerd-php crate](../developer/crates/yerd-php) - the supervisor, version resolution, and download internals. --- --- url: 'https://yerd.app/guide/code-coverage.md' --- # Code Coverage Yerd bundles [**pcov**](https://github.com/krakjoe/pcov), a fast line-coverage driver, with every PHP version it installs - so you can run your test suite with coverage (PHPUnit, Pest, `artisan test --coverage`) without installing or configuring an extension yourself. The friendliest way in is the **`yerd coverage`** subcommand: it runs your **default** PHP version with pcov enabled and forwards everything after the `coverage` subcommand straight to PHP - the same coverage mechanism as the `phpcover` shim, but discoverable from `yerd --help` without needing the shim directory on your `PATH`. Under the hood, coverage is exposed through dedicated **cover shims**: `phpcover` for your default PHP version, and `phpcover` (for example `php8.4cover`) for a specific one. They live in the same `{data}/bin` directory as the regular `php` shim. `yerd coverage` runs the same coverage mechanism as `phpcover` (default PHP + pcov); use a `phpcover` shim when you need to pin coverage to a specific version. ::: info Zero overhead by default The plain `php` and `php` shims **never** load pcov, so normal CLI scripts and your `.test` sites run with no coverage instrumentation. pcov is loaded only when you invoke a `…cover` shim - coverage is strictly opt-in, per command. ::: ## Running tests with coverage Use `yerd coverage` (or a cover shim) anywhere you'd normally use `php`: ```sh # Default PHP version, via the subcommand - args pass straight through to PHP yerd coverage artisan test --coverage yerd coverage vendor/bin/phpunit --coverage-text # The same coverage mechanism, via the shim phpcover artisan test --coverage # Pin coverage to a specific PHP version with a versioned shim php8.4cover vendor/bin/pest --coverage ``` ::: tip `yerd coverage` is a passthrough Everything after the `coverage` subcommand is handed verbatim to PHP, so flags like `--coverage` belong to your test runner, not to `yerd`. Two small edges: a leading `yerd coverage --help` prints `yerd`'s own help for the command (put `--help` after your script to forward it, e.g. `yerd coverage artisan --help`), and the global `--json` flag has no effect here - it, like every other flag, is passed to PHP rather than producing a JSON response. ::: Each cover shim points `PHPRC` at a pcov-enabled copy of Yerd's CLI ini, then hands off to your script. Because `PHPRC` is an environment variable rather than a CLI flag, it's inherited by any PHP process your script spawns in turn - which is what makes `artisan test`'s child PHPUnit/Pest/paratest run see a working coverage driver too, not just the top-level `artisan` process. ::: tip Add the shim dir to your PATH The cover shims sit in the same `{data}/bin` directory as `php` (Yerd prints the exact path). Once that's on your `PATH`, `phpcover` and `phpcover` are available everywhere, right next to the version shims described in [PHP Versions](./php-versions). ::: ## Automatic, per version You don't install or enable anything. Whenever you install a PHP version, Yerd fetches the matching pcov build for it in the background and (re)creates its cover shim. The extension is downloaded from the [`forjedio/yerd-php-ext`](https://github.com/forjedio/yerd-php-ext) releases, verified by SHA-256, and stored alongside your PHP installs at `{data}/php-ext/php-/pcov.so` - beside the install, so a PHP **patch** update never deletes it. * **`phpcover`** always tracks your [global default](./php-versions#the-global-default) version, resolved at run time - change the default with `yerd use` and `phpcover` follows. * **`phpcover`** is created for each installed version and removed when you uninstall that version. ::: info Needs a matching released build Like the [dumps extension](./laravel-dumps), pcov is ABI-specific: one build per PHP minor, per OS, per architecture. If a build for your exact PHP version and platform hasn't been published yet, the cover shim reports that pcov isn't installed for that version rather than running without coverage. The fetch is best-effort and never blocks a PHP install. ::: ::: warning No coverage on legacy PHP pcov isn't built for [legacy versions](./php-versions#legacy-php-versions) (7.4 / 8.0 / 8.1, PHP < 8.2). `phpcover`, `php7.4cover` / `php8.0cover` / `php8.1cover`, and `yerd coverage` all **error** on a legacy version rather than run. ::: ::: warning Unix only Cover shims are created on macOS and Linux only. They are not generated on other platforms. ::: ## How it works The `yerd` binary is a **multi-call** binary: before it parses any CLI arguments, it checks the name it was invoked as. The `phpcover` and `phpcover` entries in `{data}/bin` are symlinks back to `yerd` itself; when `yerd` sees one of those names, it resolves the right PHP CLI binary plus that version's `pcov.so`, writes a copy of Yerd's CLI ini with pcov's `extension`/ `pcov.enabled` directives appended, and `exec`s PHP with `PHPRC` pointing at that copy. Invoked under any other name it falls through to the normal CLI, so the clean `php`/`php` shims are untouched. `yerd coverage` reaches that **same** code path from the other direction: rather than being keyed on the invoked name, the subcommand hands its forwarded arguments to the identical cover-shim logic for the default version. So the two front doors, subcommand and shim, share one implementation. ## See also * [PHP Versions](./php-versions) - installing versions and the `php`/`php` shims. * [Laravel Dumps](./laravel-dumps) - the other extension served from `yerd-php-ext`. --- --- url: 'https://yerd.app/guide/tooling.md' --- # Tooling Yerd can install the **developer tools** a typical PHP/Laravel/WordPress project reaches for - [Composer](https://getcomposer.org), [Node.js](https://nodejs.org) (with `npm`/`npx`), [Bun](https://bun.sh), the **Laravel installer**, and **WP-CLI** - the same way it installs [PHP versions](./php-versions): self-contained binaries fetched on demand (the Laravel installer and WP-CLI are built via Composer) and dropped onto your `PATH`. No system package manager, no global install, nothing to uninstall by hand. Already have one installed elsewhere? Yerd [detects it](#external-tools) and, for most tools, uses it instead. | Tool | `id` | Provides | Source | |---|---|---|---| | Composer | `composer` | `composer` | getcomposer.org (phar) | | Node.js | `node` | `node`, `npm`, `npx` | nodejs.org (latest LTS) | | Bun | `bun` | `bun`, `bunx` | github.com/oven-sh/bun | | Laravel installer | `laravel` | `laravel` | Composer (`laravel/installer`) | | WP-CLI | `wp-cli` | `wp` | Composer (`wp-cli/wp-cli-bundle`) | ::: tip Why bundle these? A fresh machine that has Yerd shouldn't also need Homebrew, `nvm`, or a global Composer just to run a Laravel app with a Vite front-end. Yerd keeps these tools in its own data directory, isolated from anything else on your system, and removes them cleanly on uninstall. ::: ## In the desktop app Open the **Tooling** page from the sidebar (under the **Developer** group). It lists the developer tools Yerd manages and their install status: * **Composer**, **Node**, **Bun**, the **Laravel installer**, and **WP-CLI**, each showing the commands it provides. * Click **Install** to fetch the latest release; once installed you get **Update** (re-fetch the current latest) and **Uninstall**. * A tool you've installed yourself shows an **External** badge and a *Managed by you* note. **Install** is still offered, so you can take Yerd's own copy as well - see [External tools](#external-tools) below. * The Laravel installer and WP-CLI are built with Composer, so their **Install** button stays disabled until Yerd's own Composer is installed. * Each tool is placed on your `PATH` alongside PHP and managed entirely by Yerd, so it won't collide with a system install. ## From the command line ```sh yerd tools # list the tools and their install status yerd install tool node # download + install the latest Node LTS yerd install tool bun yerd install tool composer yerd install tool laravel # build the Laravel installer (needs Composer) yerd install tool wp-cli # build WP-CLI (needs Composer) yerd uninstall tool bun # remove a tool and its PATH commands ``` `yerd install tool ` is idempotent - run it again to update to the current latest. See the [Tooling CLI reference](../reference/cli/tooling) for the exact command surface. ::: tip Updating WP-CLI Because Yerd's `wp-cli` is a Composer install rather than a phar, WP-CLI's own `wp cli update` (its phar self-update subcommand) isn't applicable and will error - use `yerd install tool wp-cli` (or **Update** on the Tooling page) instead, the same way you wouldn't run `composer self-update` on Yerd's managed Composer. ::: ## External tools You don't have to let Yerd manage most of these tools. If you already have `composer`, `node`, `bun`, or the `laravel` installer available on your `PATH` - via Homebrew, `nvm`/`fnm`, a global `composer require`, etc. - Yerd **detects** it and treats it as already available: * On the **Tooling** page the tool shows an **External** badge (instead of a version) and a *Managed by you* note - it's yours to manage, not Yerd's. You can still press **Install** to take Yerd's own copy alongside it, which you'll need if you want Yerd to build the Laravel installer or WP-CLI. * The [Laravel site wizard](./sites#create-a-new-laravel-site) and site scaffolding accept external Composer / Node / Bun / Laravel as satisfying their prerequisites, so you won't be asked to install a second copy. Externally installed Composer and the Laravel installer still run under the **Yerd PHP version you select**, so versions stay consistent. A couple of things to know: * **WP-CLI is the exception: an external `wp` doesn't count.** Yerd runs WP-CLI by executing its own build directly rather than by calling `wp` on your `PATH`, so it always needs its own copy and never reports one you installed yourself as *External*. Your `wp` keeps working exactly as before; the [WordPress site wizard](./sites#create-a-new-wordpress-site) will simply offer to install Yerd's WP-CLI as a prerequisite. * **Managed tools win.** If a tool is both Yerd-installed and on your `PATH`, the Yerd-managed one takes precedence (its `{data}/bin` shim is earlier on `PATH`). That includes `composer`, so installing Yerd's copy changes which `composer` your other projects get. * **Building the *managed* Laravel installer or WP-CLI needs Yerd's own Composer.** An external Composer is fine for *scaffolding*, but it can't build Yerd's managed `laravel`/`wp-cli` tools - so their **Install** stays disabled until you install Yerd's Composer. For the Laravel installer you can skip that entirely and keep using your external copy; WP-CLI has no such fallback, per the exception above. ::: tip How detection works Because the daemon runs with a minimal environment, Yerd reads your login shell's `PATH` to find tools your terminal can see (Homebrew, `fnm`, a global Composer bin, …). It only looks **outside** its own `{data}/bin`, so a Yerd shim is never mistaken for an external install. ::: ## How it works The model mirrors [PHP versions](./php-versions) and [services](./services): * **Self-contained binaries.** Each tool is a relocatable build - Node's tarball bundles `node` + `npm` + `npx`, Bun is a single binary, Composer is a phar run by Yerd's managed PHP. Nothing is compiled and nothing touches system paths. * **Verified downloads.** Every artifact is checked against the publisher's `SHASUMS256.txt` (Node, Bun) or `composer.phar.sha256sum` (Composer) before it is installed. * **Installed under Yerd's data dir.** Tools live in `{data}/tools//` (e.g. `~/Library/Application Support/io.yerd.Yerd/tools` on macOS), a sibling of your PHP installs - so a PHP update never disturbs them. * **Exposed on `PATH`.** Their commands are symlinked into `{data}/bin`, the same directory that holds the `php`/`php` shims. Put that directory on your `PATH` once (see below) and `composer`, `node`, `npm`, `bun`, … just work. * **Rootless.** Everything runs as your user, no elevation. ### Latest only Yerd installs the **latest stable** release of each tool (the latest **LTS** for Node). There is no per-project version picker - **Update** simply re-fetches the current latest and replaces it in place. If you need to pin a specific Node version per project, a system version manager like `nvm`/`fnm` is still the right tool; Yerd's goal here is a good default that's always there. ## Put Yerd's bin directory on your PATH The tool commands live in Yerd's `{data}/bin` directory. Installing your first tool from the CLI **adds it to your shell automatically** - so usually there's nothing to do. If you installed via the desktop app, or want to manage the entry yourself, run it once: ```sh yerd path install # adds {data}/bin to your shell startup file ``` Open a new terminal afterwards (or `source` your shell file). Then: ```sh which composer # → …/io.yerd.Yerd/bin/composer node --version npm --version bun --version ``` `yerd path install` writes a small, guarded block to your shell's startup file (`.zshrc`, `.bashrc`/`.bash_profile`, or `config.fish`). `yerd path uninstall` removes it; `yerd path print` shows the snippet without touching any file. ::: info Coexisting with Herd, Homebrew, or nvm Yerd's `bin` directory is **prepended** to `PATH`, so its `node`/`composer` take precedence over other copies on your machine. If you'd rather your existing tools win, put their directories earlier in your shell file. Nothing Yerd installs ever shadows a tool you didn't ask it to manage. ::: ## Composer needs PHP Composer is a phar, so it runs under Yerd's managed PHP - `composer` resolves to your [default PHP version](./php-versions). Install at least one PHP version first (`yerd install php 8.4`); otherwise `composer` reports that no PHP is available. Node and Bun are standalone and have no such dependency. ::: tip ext-intl and friends Yerd's PHP builds ship the **bulk** extension set, including `intl`, `sodium`, `mysqli`, and more - so Composer packages that require them install without extra steps. See [PHP Versions](./php-versions) for the bundled extension list. ::: ## Where things live | Path | Contents | |---|---| | `{data}/tools/composer/composer.phar` | The Composer phar. | | `{data}/tools/node/node---/` | The unpacked Node distribution. | | `{data}/tools/bun/bun--/bun` | The Bun binary. | | `{data}/tools/laravel/bin/laravel` | The Laravel installer (built via Composer). | | `{data}/tools/wp-cli/vendor/wp-cli/wp-cli/php/boot-fs.php` | WP-CLI (built via Composer). | | `{data}/bin/{composer,node,npm,npx,bun,bunx,laravel,wp}` | The `PATH` shims. | `{data}` is Yerd's per-user data directory (`yerd status` and `yerd path print` both show the exact path for your platform). ## See also * [Tooling CLI reference](../reference/cli/tooling) - every command and flag. * [PHP Versions](./php-versions) - the version model these tools follow. * [Services & Databases](./services) - the same install-on-demand approach for databases and caches. --- --- url: 'https://yerd.app/guide/services.md' --- # Services & Databases Yerd installs and supervises local **database, cache, and search** engines as native, per-user processes - the way [DBngin](https://dbngin.com) does, but folded into the same [`yerdd` daemon](./daemon) that already runs your sites, PHP, HTTPS, and DNS. No Docker, no containers, no VM. A single `yerd status` shows the whole stack. The five engines: | Service | `id` | Kind | Default port | |---|---|---|---| | Redis (Valkey) | `redis` | Cache / key-value | 6379 | | MySQL | `mysql` | SQL database | 3306 | | MariaDB | `mariadb` | SQL database | 3306 | | PostgreSQL | `postgres` | SQL database | 5432 | | Meilisearch | `meilisearch` | Search index | 7700 | ::: info Redis is served by Valkey The `redis` slot is filled by **Valkey**, the BSD-licensed fork, because recent Redis releases are no longer cleanly redistributable. It is wire-compatible, so your Redis clients work unchanged. Yerd shows it as `Redis (Valkey)`. ::: ::: tip Engine availability All five engines are implemented end-to-end. Whether a specific engine/version installs depends on whether a prebuilt build is published for your platform in Yerd's hosted distribution - run `yerd service available` to see what you can install right now. MySQL/MariaDB share port 3306, so only one can be enabled on it at a time. ::: ## How it works Service support follows the same model as [PHP versions](./php-versions): * **Native processes, not Docker.** Prebuilt binaries are downloaded on demand from Yerd's own hosted distribution, then run as your user on loopback. * **Supervised.** `yerdd` runs one process per enabled service, restarts it on crash with backoff, and reports health - the same supervision substrate the PHP pools use ([`yerd-supervise`](../developer/crates/yerd-supervise)). * **Rootless.** Everything runs as your user with no elevation. See the [privilege model](./elevation). * **Local-dev posture.** Engines bind to loopback only and accept passwordless connections from your user. This is convenient for local development and is not meant to be exposed to a network. ## In the desktop app The [desktop app](./desktop-app) surfaces every engine on its **Services** page, under the **Developer** group in the sidebar. Install a version, then Start / Stop / Restart it inline - no terminal needed. The daemon auto-starts every installed engine on boot, so what you install stays running across reboots. Each installed engine's `⋯` menu offers: * **Configuration** - copies a ready-made Laravel `.env` for that engine, with a database picker that pre-fills `DB_DATABASE` for the SQL engines. * **Edit port** - change the loopback port (applies on next start). * **View logs** - tail the service log. * **Manage databases** - create, drop, back up, and restore databases (SQL engines only). * **Change version** - upgrade in place, keeping your data. * **Uninstall** - remove the engine. ## From the command line ### Managing services ```sh yerd service available # versions installable for your platform yerd service install redis 8 # download, install, and start yerd service install postgres 17-full # PostGIS build (see below) yerd services # list everything: version, state, port yerd service start redis # start it now yerd service stop redis # stop for this session (returns on next daemon start) yerd service restart redis yerd service set-port redis 6380 # change the loopback port (next start) yerd service logs redis --lines 50 # tail the service log yerd service change-version redis 8.1 # upgrade in place, keep data yerd service uninstall redis 8 # remove binaries, keep data yerd service uninstall redis 8 --purge # remove binaries AND data ``` See the [Services CLI reference](../reference/cli/services) for every flag. ### Managing databases For the SQL engines (`mysql`, `mariadb`, `postgres`), Yerd can create, drop, list, back up, and restore databases without you reaching for a separate client. The engine must be running. ```sh yerd db create mysql my_app yerd db list mysql yerd db backup mysql my_app ./my_app.sql # plain-SQL dump yerd db restore mysql my_app ./my_app.sql # replay into an existing db yerd db drop mysql my_app ``` Database names are validated to a strict allowlist (letters, digits, and underscores; must start with a letter or `_`; at most 63 characters) so the generated SQL is injection-proof. Engine-internal databases are protected and can't be dropped. `backup` writes to a temp file and atomically renames it, so a failed dump never clobbers an existing one. See the [Databases CLI reference](../reference/cli/db) for details. ## Configuration Installed services are recorded in your [config file](../reference/configuration) under per-service `[services.]` tables, each carrying the pinned `version`, the `port`, and an `enabled` flag (a record of the last start/stop intent): ```toml [services.redis] version = "8" port = 6379 enabled = true ``` You normally don't hand-edit this - drive it through the CLI (or the [desktop app](./desktop-app)), which keeps the config and the running processes in sync. ### Auto-start on boot The daemon auto-starts **every installed engine** when it starts (in the background, so a slow database cold-boot never delays the proxy or DNS). The `enabled` flag does **not** gate this - a service you `stop` returns on the next daemon start. To keep an engine off for good, `uninstall` it. ::: tip MySQL and MariaDB share port 3306 Only one can listen on `3306` at a time. If both are installed, whichever binds first wins and the other logs a non-fatal "port in use" and stays down. Run a single SQL engine, or give one a different `port`. ::: ## PostgreSQL: base and PostGIS (`full`) builds PostgreSQL ships in **two flavours**, and you choose which one you install by its version label. `full` is the PostGIS variant, appended to the version as a `-full` label: | Label | What you get | Compressed size | License | |---|---|---|---| | `17` | The lean base build. | ≈ 6.5 MB | 100% PostgreSQL License (permissive) | | `17-full` | The base plus **PostGIS** and its geospatial stack. | ≈ 60-64 MB | GPL-encumbered (see below) | ```sh yerd service install postgres 17 # lean base yerd service install postgres 17-full # PostGIS build ``` Both show up as distinct installable versions in `yerd service available` and in the desktop app's version picker. The build is downloaded once per install and cached. ### What each build bundles Both builds ship the standard contrib extensions - `pg_stat_statements`, `pg_trgm`, `citext`, `unaccent`, `hstore`, `ltree`, `btree_gin`, `btree_gist`, `fuzzystrmatch`, `tablefunc`, `intarray`, `cube`, `earthdistance`, `postgres_fdw`, `dblink`, `pageinspect`, `amcheck`, `pgstattuple`, `pg_buffercache` - plus **`pgvector`** (Linux and macOS). The **`full`** build adds the geospatial and crypto stack on top: * **PostGIS** with raster and topology: `postgis`, `postgis_raster`, `postgis_topology`, `postgis_tiger_geocoder`, `address_standardizer`. * `pgcrypto`, `uuid-ossp`, `sslinfo`, and `xml2`. ### Base and `full` share one datadir - switch between them freely `17` and `17-full` are separate *installs* but **share a single data directory** (Postgres datadirs are pinned to the major version, and the `full` variant maps to the same major). So you can install the base build, create databases, then switch with `yerd service change-version postgres 17-full` (or back) **without losing your data** - the databases carry across the switch. One caveat: PostGIS objects created while running `full` need the PostGIS `.so` to be *used*. The base build still starts against the shared datadir and your regular tables are fine, but queries that touch PostGIS types or functions only work while `full` is running - so enable `full` before you start using PostGIS. Uninstalling with `--purge` deletes the shared datadir, so `yerd service uninstall postgres