Changelog¶
All notable changes to Lerd will be documented here.
The format follows Keep a Changelog. Lerd uses Semantic Versioning.
[1.0.2] — 2026-03-25¶
Added¶
- RustFS replaces MinIO — MinIO OSS is no longer maintained; lerd now ships RustFS as its built-in S3-compatible object storage service. RustFS exposes the same API and credentials (
lerd/lerdpassword) so no application changes are needed. lerd minio:migrate— one-command migration from an existing MinIO installation to RustFS. Stops the MinIO container, copies data to the RustFS data directory, removes the MinIO quadlet, updatesconfig.yaml, and starts RustFS. The original MinIO data directory is preserved for manual cleanup.- Auto-migration prompt during
lerd update— if a MinIO data directory is detected at update time, lerd offers to run the migration automatically before continuing. lerd.localhostcustom domain — the Lerd dashboard is now accessible athttp://lerd.localhost(nginx proxies the domain to the UI service).lerd dashboardopens the new URL..localhostresolves to127.0.0.1natively on all modern systems with no DNS configuration.- Installable PWA — the dashboard ships a web app manifest (
/manifest.webmanifest) and SVG icons so it can be installed as a standalone app from Chrome or other PWA-capable browsers.
Fixed¶
- 502 Bad Gateway on Inertia.js full-page refreshes — nginx vhost templates now include
fastcgi_buffers 16 16kandfastcgi_buffer_size 32k, preventingupstream sent too big headererrors caused by large FastCGI response headers (common on routes with heavy session/flash data).
[1.0.1] — 2026-03-25¶
Added¶
lerd shell— opens an interactiveshsession inside the project's PHP-FPM container. The PHP version is resolved the same way as every other lerd command (.php-version,composer.json, global default). The working directory is set to the site root. If the site is paused, any services referenced in.envare started automatically before the shell opens.- Shell completions auto-installed on
lerd install— fish completions are written to~/.config/fish/completions/lerd.fish; zsh completions to~/.zfunc/_lerdwith the requiredfpathandcompinitlines appended to.zshrc; bash completions to~/.local/share/bash-completion/completions/lerd. - Pause/unpause propagates to git worktrees — when a site is paused, all its worktree checkouts also receive a paused nginx vhost with a Resume button. The button targets the parent site so clicking it unpauses both the parent and all worktrees at once. Unpausing restores all worktree vhosts and removes the paused HTML files.
Fixed¶
lerd parkrefuses to park a framework project root — if the target directory is itself a Laravel/framework project, lerd now prints a helpful message and suggestslerd linkinstead of silently misbehaving.lerd parkno longer registers framework subdirectories as sites — when a project root is accidentally used as a park directory, subdirectories likeapp/,vendor/, andpublic/are now skipped with a warning rather than being registered as phantom sites.
[1.0.0] — 2026-03-25¶
Added¶
-
Laravel Horizon support — lerd auto-detects
laravel/horizonincomposer.jsonand provides dedicatedlerd horizon:start/lerd horizon:stopcommands that runphp artisan horizonas a persistent systemd user service (lerd-horizon-{site}). When Horizon is detected, the Queue toggle in the web UI is replaced by a Horizon toggle, and a Horizon log tab appears in the site detail panel while Horizon is running. Pause/unpause correctly stops and resumes the Horizon service alongside other workers. MCP toolshorizon_startandhorizon_stopprovide the same control to AI assistants. -
Service dependencies (
depends_on) — custom services can now declare which services they depend on. Starting a service with dependencies starts those dependencies first; starting a dependency automatically starts any services that depend on it; stopping a dependency cascade-stops its dependents first. Declare via thedepends_onYAML field, the--depends-onflag onlerd service add, or thedepends_onparameter in theservice_addMCP tool. -
lerd man— terminal documentation browser — browse and search the built-in docs without leaving the terminal. Opens an interactive TUI with arrow-key navigation, live filtering by title or content, and a scrollable markdown pager. Pass a page name to jump directly (e.g.lerd man sites). SetGLAMOUR_STYLE=lightto override the default dark theme. Works in non-TTY mode too:lerd man | catprints a table of contents andlerd man sites | catprints raw markdown. lerd about— prints the version, build info, project URL, and copyright.-
CLI commands auto-start services on paused sites — running
php artisan,composer,lerd db:export,lerd db:import, orlerd db:shellin a paused site's directory automatically starts any services the site needs (MySQL, Redis, etc.) before executing. A notice is printed only when a service actually needs starting; if services are already running the command executes silently. The site stays paused — no vhost restore or worker restart. -
lerd pause/lerd unpause— pause a site without unlinking it.lerd pausestops all running workers (queue, schedule, reverb, stripe, and any custom workers), replaces the nginx vhost with a static landing page, and auto-stops any services no longer needed by other active sites. The paused state persists acrosslerd start/lerd stopcycles.lerd unpauserestores the vhost, restarts any services the site's.envreferences, and resumes all workers that were running before the pause. The landing page includes a Resume button that calls the lerd API directly so you can unpause from the browser. lerd service pin/lerd service unpin— pin a service so it is never auto-stopped, even when no active sites reference it in their.env. Pinning immediately starts the service if it isn't already running. Unpin to restore normal auto-stop behaviour.- Extra ports on built-in services —
lerd service expose <service> <host:container>publishes an additional host port on any built-in service (mysql, redis, postgres, meilisearch, minio, mailpit). Mappings are persisted in~/.config/lerd/config.yamlunderservices.<name>.extra_portsand applied on every start. The service is restarted automatically if running. Use--removeto delete a mapping. - Reverb nginx WebSocket proxy — when a site uses Laravel Reverb (detected via
composer.jsonorBROADCAST_CONNECTION=reverbin.env), lerd adds a/applocation block to the nginx vhost that proxies WebSocket upgrade requests to the Reverb server running on port 8080 inside the PHP-FPM container. The block is added automatically onlerd linkand onreverb:start. - Framework definitions — user-defined PHP framework YAML files at
~/.config/lerd/frameworks/<name>.yaml. Each definition describes detection rules, the document root, env file format, per-service env detection/variable injection, and background workers.lerd framework list/add/removemanage definitions from the CLI. - Framework workers — frameworks can define named background workers (e.g.
messengerfor Symfony,horizonorpulsefor Laravel) that run as systemd user services inside the PHP-FPM container.lerd worker start <name>/lerd worker stop <name>/lerd worker listmanage them. - Custom workers for Laravel — the built-in Laravel definition includes
queue,schedule, andreverbworkers. Additional workers (e.g. Horizon, Pulse) can be added vialerd framework add laravel. - Generic
lerd workercommand —lerd worker start/stop/listworks for any framework-defined worker.lerd queue:start,lerd schedule:start, andlerd reverb:startare now aliases forlerd worker start queue/schedule/reverband work on any framework with those workers, not just Laravel. - Web UI: framework worker toggles — custom framework workers appear as indigo toggles in the Sites panel alongside queue/schedule/reverb. Each running worker shows a log tab in the site detail drawer and an indicator dot in the site list.
- Web UI: docs link — a "Docs" link in the dashboard navbar opens the documentation site.
- MCP
worker_start/worker_stop/worker_list— start, stop, or list framework-defined workers for a site via the MCP server. - MCP
framework_list/framework_add/framework_remove— manage framework definitions from an AI assistant.framework_addwithname: "laravel"adds custom workers to the built-in Laravel definition. - MCP
service_expose— publish or remove an extra host port on a built-in service via the MCP server. - MCP
site_pause/site_unpausetools — AI agents can pause and resume sites directly. - MCP
service_pin/service_unpintools — AI agents can pin services to keep them always available. - MCP
sitesnow includes framework and workers — each site entry includes itsframeworkname and aworkersarray with running status per worker.
Changed¶
-
lerd service listuses a compact two-column format — theTypecolumn has been removed. Custom services show[custom]inline after their status. Inactive reason anddepends on:info now appear as indented sub-lines, keeping the output narrow on small terminals. -
lerd service list/lerd service statusshows inactive reason — when a service is inactive, the output now includes a short note:(no sites using this service)for auto-stopped services, or(start with: lerd service start <name>)for manually stopped ones. lerd logsaccepts a site name as target — pass a registered site name to get logs for that site's PHP-FPM container (e.g.lerd logs my-project). Previously only nginx, service names, and PHP version strings were accepted.lerd unlinkauto-stops unused services — after unlinking a site, any services that were only needed by that site are automatically stopped (respecting pin and manually-started flags).db:importanddb:exportaccept a-d/--databaseflag — both commands now accept an optional--database/-dflag to target a specific database. When omitted the database name falls back toDB_DATABASEfrom the project's.env. The MCPdb_exporttool gains the same optionaldatabaseargument.lerd secure/lerd unsecurerestart the Stripe listener — if alerd stripe:listenservice is active when HTTPS is toggled, it is automatically restarted with the updated forwarding URL so--forward-tostays in sync with the site's scheme.- MinIO: per-site bucket created by
lerd env— when MinIO is detected,lerd envcreates a bucket named after the site handle (e.g.my_project), sets it to public access, and writesAWS_BUCKET=<site>andAWS_URL=http://localhost:9000/<site>into.env. reverb:startregenerates the nginx vhost — runninglerd reverb:start(or toggling Reverb in the web UI) now regenerates the site's nginx config and reloads nginx, ensuring the/appWebSocket proxy block is added to existing sites without requiringlerd linkto be re-run.lerd envsets correct Reverb connection values —REVERB_HOST,REVERB_PORT, andREVERB_SCHEMEare now derived from the site's domain and TLS state instead of hardcodedlocalhost:8080.VITE_REVERB_*vars are also written to match.queue_start/schedule_start/reverb_startare no longer Laravel-only — these CLI commands and MCP tools now work for any framework that defines a worker with that name.lerd envrespects framework env configuration — uses the framework's configured env file, example file, format,url_key, and per-service detection rules instead of hardcoded Laravel paths.lerd link/lerd parkdetect and record the framework — the detected framework name is stored in the site registry and shown inlerd sites.
Fixed¶
lerd phpandlerd artisanno longer break MCP stdio transport — both commands now allocate a TTY (-t) only when stdin is a real terminal. When invoked by MCP or any other pipe-based tool, the TTY flag is omitted so stdin/stdout remain clean byte streams.- Reverb toggle no longer appears on projects that don't use Reverb — the UI previously showed the Reverb toggle for all Laravel sites. It now gates on whether
laravel/reverbis incomposer.jsonorBROADCAST_CONNECTION=reverbis in.env.
[0.9.1] — 2026-03-22¶
Added¶
- MCP
service_envtool — returns the recommended Laravel.envconnection variables for any service (built-in or custom) as a key/value map. Agents can callservice_env(name: "mysql")to inspect connection settings without runningenv_setupor modifying.env. Works for all six built-in services and any custom service registered viaservice_add.
Changed¶
lerd updatedoes a fresh version check — bypasses the 24-hour update cache and always fetches the latest release tag from GitHub directly. After a successful update the cache is refreshed solerd statusandlerd doctorstop showing a stale "update available" notice.lerd updateignores git-describe suffixes — dev/dirty builds (e.g.v0.9.0-dirty) are now treated as equal to the corresponding release when comparing versions, so locally-built binaries no longer trigger a spurious update prompt.
[0.9.0] — 2026-03-22¶
Added¶
lerd doctorcommand — full environment diagnostic. Checks podman, systemd user session, linger, quadlet/data dir writability, config validity, DNS resolution, port 80/443/5300 conflicts, PHP-FPM image presence, and update availability. Reports OK/FAIL/WARN per check with a hint for every failure and a summary line at the end.lerd statusshows watcher and update notice —lerd-watcheris now included in the status output alongside DNS, nginx, and PHP-FPM. A highlighted banner is printed when a newer version is cached.- Background update checker — checks GitHub for a new release once per 24 hours; result is cached to
~/.local/share/lerd/update-check.json. Fetches relevant CHANGELOG sections between the current and latest version. Used bylerd status,lerd doctor, the web UI, and the system tray. - MCP
statustool — returns structured JSON with DNS (ok + tld), nginx (running), PHP-FPM per version (running), and watcher (running). Recommended first call when a site isn't loading. - MCP
doctortool — runs the fulllerd doctordiagnostic and returns the text report. Use when the user reports setup issues or unexpected behaviour. - Watcher structured logging — the watcher package now uses
slogthroughout. SetLERD_DEBUG=1in the environment to enable debug-level output at runtime; watcher is otherwise silent except for WARN/ERROR events. - Web UI: Watcher card — the System tab now shows whether
lerd-watcheris running. When stopped, a Start button appears to restart it without opening a terminal. The card also streams live watcher logs (DNS repair events, fsnotify errors, worktree timeouts) directly in the browser. - Web UI: grouped worker accordions — queue workers, schedule workers, Stripe listeners, and Reverb servers are now grouped into collapsible accordions on the Services tab. Click a group header to expand it; only one group is open at a time. Mobile pill navigation is split into core services + group toggle pills with expandable sub-rows.
- Tray: update badge — the "Check for update..." menu item shows "⬆ Update to vX.Y.Z" when a new version is cached. Per-site workers (queue, schedule, Stripe, Reverb) are no longer listed in the tray services section.
Changed¶
lerd updateshows changelog and asks for confirmation — before downloading anything,lerd updatenow fetches and prints the CHANGELOG sections for every version between the current and latest release, then promptsUpdate to vX.Y.Z? [y/N]. The update only proceeds on an explicity/yes; pressing Enter or anything else cancels.
Fixed¶
lerd startnow startslerd-watcher— the watcher service was missing from the start sequence and could only be stopped bylerd quit, never started.lerd startnow includes it alongsidelerd-ui.
[0.8.2] — 2026-03-21¶
Fixed¶
- 413 Request Entity Too Large on file uploads — nginx now sets
client_max_body_size 0(unlimited) in thehttpblock, applied to all vhosts.lerd startalso rewritesnginx.confon every start so future config changes take effect without runninglerd install. - MCP
logstarget accepts site domains — site names containing dots (e.g.astrolov.com) were incorrectly matched as PHP version strings, producing invalid container names. The PHP version check now requires the strict pattern\d+\.\d+. - MinIO
AWS_URLset to public endpoint —AWS_URLis nowhttp://localhost:9000(browser-reachable) instead ofhttp://lerd-minio:9000(internal container hostname).AWS_ENDPOINTis unchanged and remains the internal address used by PHP. - Services page no longer blinks — the services list was polling every 5 seconds regardless of which tab was active, and showed a loading spinner on each poll. Polling now only runs while the services tab is visible, and the spinner only shows on the initial load.
Added¶
- DNS health watcher — the
lerd-watcherdaemon now polls.testDNS resolution every 30 seconds. When resolution breaks, it waits forlerd-dnsto be ready and re-applies the resolver configuration, replicating the repair performed bylerd start. Uses the configured TLD (dns.tldin global config, defaulttest). - MCP
logstarget is optional — whentargetis omitted, logs for the current site's PHP-FPM container are returned (resolved fromLERD_SITE_PATH). Specifytargetonly to view a different service or site.
Changed¶
make installrespects manually-stopped services —lerd-ui,lerd-watcher, andlerd-trayare only restarted after install if they were already running. Services stopped vialerd quitare left stopped.
[0.8.1] — 2026-03-21¶
Fixed¶
- MCP
service_start/service_stopaccept custom services — the MCP tool schema previously restricted thenamefield to an enum of built-in services, causing AI assistants to refuse to call these tools for custom services added viaservice_add. The enum constraint has been removed; any registered service name is now valid.
Changed¶
- MCP SKILL and guidelines updated —
soketiremoved from the built-in service list (dropped in v0.8.0);service_start/service_stopdescriptions clarified to explicitly mention custom service support.
[0.8.0] — 2026-03-21¶
Added¶
lerd reverb:start/reverb:stop— runs the Laravel Reverb WebSocket server as a persistent systemd user service (lerd-reverb-<site>.service), executingphp artisan reverb:startinside the PHP-FPM container. Survives terminal sessions and restarts on failure. Also available aslerd reverb start/lerd reverb stop.lerd schedule:start/schedule:stop— runs the Laravel task scheduler as a persistent systemd user service (lerd-schedule-<site>.service), executingphp artisan schedule:work. Also available aslerd schedule start/lerd schedule stop.lerd dashboard— opens the Lerd dashboard (http://127.0.0.1:7073) in the default browser viaxdg-open.- Auto-configure
REVERB_*env vars —lerd envnow generatesREVERB_APP_ID,REVERB_APP_KEY,REVERB_APP_SECRET, andREVERB_HOST/PORT/SCHEMEvalues whenBROADCAST_CONNECTION=reverbis detected, using random secure values for secrets. lerd setuprunsstorage:link— setup now runsphp artisan storage:linkwhen the site'sstorage/app/publicdirectory is not yet symlinked.lerd setupstarts the queue worker — setup now startsqueue:startas a final step whenQUEUE_CONNECTION=redisis set in.envor.env.example.- Watcher triggers
queue:restarton config changes — the watcher daemon monitors.env,composer.json,composer.lock, and.php-versionin every registered site and signalsphp artisan queue:restartwhen any of those files change (debounced). lerd start/stopmanage schedule and reverb —lerd startandlerd stopnow include alllerd-schedule-*andlerd-reverb-*service units in their start/stop sequences alongside queue workers and stripe listeners.- MCP tools for reverb, schedule, stripe — new
reverb_start,reverb_stop,schedule_start,schedule_stop, andstripe_listentools exposed via the MCP server. - Web UI: schedule and reverb per-site — the site detail panel shows whether the schedule worker and Reverb server are running, with start/stop buttons and live log streaming.
- Web UI:
stripe:stopaction — the dashboard now supports stopping a stripe listener from the site action menu (was start-only).
Changed¶
- Queue worker uses
Restart=always— thelerd-queue-*service unit now restarts unconditionally (wasRestart=on-failure). lerd.testdashboard vhost removed —lerd installno longer generates an nginx proxy vhost forlerd.test. The dashboard is only accessible athttp://127.0.0.1:7073.- Web UI queue/stripe start is non-blocking —
queue:startandstripe:listensite actions now run in a background goroutine.
Removed¶
- Soketi service removed — Soketi has been removed from Lerd's service list. Laravel Reverb (
lerd reverb:start) is the recommended WebSocket solution.
[0.7.0] — 2026-03-21¶
Added¶
lerd quitcommand — fully shuts down Lerd: stops all containers and services (likelerd stop), then also stops thelerd-uiandlerd-watcherprocess units, and kills the system tray.- Start/Stop from the web UI — the dashboard now has Start and Stop buttons via new
/api/lerd/start,/api/lerd/stop, and/api/lerd/quitAPI endpoints. lerd startresumes stripe listeners —lerd-stripe-*services are now included in the start sequence alongside queue workers and the UI service.
Changed¶
- Tray quit uses
lerd quit— the tray's quit action now calls the newquitcommand, ensuring a full shutdown including the UI and watcher processes. lerd stopstops all services regardless of pause state — stop now shuts down all installed services including paused ones and stripe listeners.
Fixed¶
- Log panel guards — clicking to open logs for FPM, nginx, DNS, or queue services no longer attempts to open a log stream when the service is not running.
0.6.0 — 2026-03-21¶
Added¶
- Git worktree support — each
git worktreecheckout automatically gets its own subdomain (<branch>.<site>.test) with a dedicated nginx vhost. No manual steps required. - The watcher daemon detects
git worktree add/git worktree removein real time via fsnotify and generates or removes vhosts accordingly. It watches.git/itself so it correctly re-attaches when.git/worktrees/is deleted (last worktree removed) and re-created (new worktree added). - Startup scan generates vhosts for all existing worktrees across all registered sites.
EnsureWorktreeDeps— symlinksvendor/andnode_modules/from the main repo into each worktree checkout, and copies.envwithAPP_URLrewritten to the worktree subdomain.lerd sitesshows worktrees indented under their parent site.- The web UI shows worktrees in the site detail panel with clickable domain links and an open-in-browser button.
- A git-branch icon appears on the site button in the sidebar whenever the site has active worktrees.
- HTTPS for worktrees — when a site is secured with
lerd secure, all its worktrees automatically receive an SSL vhost that reuses the parent site's wildcard mkcert certificate (*.domain.test). No separate certificate is needed per worktree. Securing and unsecuring a site also updatesAPP_URLin each worktree's.env. - Catch-all default vhost (
_default.conf) — any.testhostname that does not match a registered site returns HTTP 444 / rejects the TLS handshake, instead of falling through to the first alphabetical vhost. stripe:listenas a background service —lerd stripe:listennow runs the Stripe CLI in a persistent systemd user service (lerd-stripe-<site>.service) rather than a foreground process. It survives terminal sessions and restarts on failure.lerd stripe:listen stoptears it down.- Service pause state —
lerd service stopnow records the service as manually paused.lerd startand autostart on login skip paused services.lerd stop+lerd startrestore the previous state: running services restart, manually stopped services stay stopped. - Queue worker Redis pre-flight —
lerd queue:startchecks thatlerd-redisis running whenQUEUE_CONNECTION=redisis set in.env, and returns a friendly error with instructions rather than failing with a cryptic DNS error from PHP.
Fixed¶
- Park watcher depth — the filesystem watcher no longer registers projects found in subdirectories of parked directories. Only direct children of a parked directory are eligible for auto-registration.
- Nginx reload ordering for secure/unsecure —
lerd secure/lerd unsecure(and their UI/MCP equivalents) now save the updatedsecuredflag tosites.yamlbefore reloading nginx. Previously a failed nginx reload would leavesites.yamlwith a stalesecuredstate, causing the watcher to regenerate the wrong vhost type on restart. - Tray always restarts on
lerd start— any existing tray process is killed before relaunching, preventing duplicate tray instances after repeatedlerd startcalls. - FPM quadlet skip-write optimisation —
WriteFPMQuadletskips writing and daemon-reloading when the quadlet content is unchanged. Unnecessary daemon-reloads caused Podman's quadlet generator to regenerate all service files, which could briefly disruptlerd-dnsand cause.testresolution failures.
0.5.16 — 2026-03-20¶
Fixed¶
- PHP-FPM image build on restricted Podman — fully qualify all base image names in the Containerfile (
docker.io/library/composer:latest,docker.io/library/php:X.Y-fpm-alpine). Systems without unqualified-search registries configured in/etc/containers/registries.confwould fail with "short-name did not resolve to an alias".
0.5.15 — 2026-03-20¶
Fixed¶
- PHP-FPM image build on Podman — the Containerfile now declares
FROM composer:latest AS composer-binas an explicit stage before copying the composer binary. Podman (unlike Docker) does not auto-pull images referenced only inCOPY --from, causing builds to fail with "no stage or image found with that name". This also affectedlerd updateandlerd php:rebuildin v0.5.14, leaving containers stopped if the build failed after the old image was removed. - Zero-downtime PHP-FPM rebuild —
lerd php:rebuildno longer removes the existing image before building. The running container stays up during the build; only the finalsystemctl restartcauses a brief interruption. Force rebuilds now use--no-cacheinstead ofrmi -f. - UI logs panel — clicking logs for a site whose PHP-FPM container is not running now shows a clean "container is not running" message instead of the raw podman error.
lerd php/lerd artisan— running these when the PHP-FPM container is stopped now returns a friendly error with thesystemctl --user startcommand instead of a raw podman error.lerd updateensures PHP-FPM is running — after applying infrastructure changes,lerd updatenow starts any installed PHP-FPM containers that are not running. Also fixed a cosmetic bug where "skipping rebuild" was printed even when a rebuild had just run.
0.5.14 — 2026-03-20¶
Added¶
LERD_SITE_PATHin MCP config —mcp:injectnow embeds the project path asLERD_SITE_PATHin the injected MCP server config. The MCP server reads this at startup and uses it as the defaultpathforartisan,composer,env_setup,db_export, andsite_link, so AI assistants no longer need to pass an explicit path on every call..ai/mcp/mcp.jsoninjection —mcp:injectnow also writes into.ai/mcp/mcp.json(used by Windsurf and other MCP-compatible tools), in addition to.mcp.jsonand.junie/mcp/mcp.json.
0.5.13 — 2026-03-20¶
Fixed¶
lerd mcp:inject—db_exporttool now correctly appears in the generated SKILL.md and.junie/guidelines.md(skill content was omitted from the v0.5.12 release)
0.5.12 — 2026-03-20¶
Added¶
- MCP:
db_export— export the project database to a SQL dump file (defaults to<database>.sqlin the project root); reads connection details from.env
Fixed¶
lerd artisan/lerd php/lerd node/lerd npm/lerd npx— lerd usage/help text and "Error: exit status N" no longer appear when the subprocess exits with a non-zero code (e.g. failed tests); only the subprocess output is shown and the original exit code is propagated to the shell
0.5.11 — 2026-03-20¶
Added¶
- MCP: 14 new tools — the
lerd mcpserver now exposes the full project lifecycle: composer— run Composer inside the PHP-FPM containernode_install/node_uninstall— install or uninstall Node.js versions via fnmruntime_versions— list installed PHP and Node.js versions with defaultsenv_setup— configure.envfor lerd (detects services, starts them, creates DB, generatesAPP_KEY, setsAPP_URL)site_link/site_unlink— register or unregister a directory as a lerd sitesecure/unsecure— enable or disable HTTPS for a site; updatesAPP_URLautomaticallyxdebug_on/xdebug_off/xdebug_status— toggle Xdebug per PHP version and check stateservice_add/service_remove— register or deregister custom OCI services- MCP:
service_start/service_stopsupport custom services — previously only worked for built-in services - MCP:
.junie/guidelines.md—lerd mcp:injectnow writes a lerd context section into Junie's guidelines file (merged, not overwritten) so JetBrains Junie has the same tool knowledge as Claude Code - Web UI: tab persistence — active tab (Sites, Services, System) is now stored in the URL hash (
/#services) so refreshing the browser returns to the same tab
Fixed¶
- MCP skill content updated with all new tools, workflows, and architecture notes
0.5.9 — 2026-03-20¶
Added¶
lerd node:install <version>— install a Node.js version globally via fnmlerd node:uninstall <version>— uninstall a Node.js version via fnm- Node.js card in System tab — lists all installed Node versions with an inline install form; replaces the install form that was previously in the Services tab
lerd php:rebuildnow restarts containers — automatically restarts all FPM containers after rebuilding images instead of printing manual instructions
Fixed¶
lerd traynot opening after update —install.sh --updatewas not copying thelerd-trayhelper binary alongsidelerdlaravel newand other PHP CLI tools now work end-to-end — the PHP-FPM container image now includes Composer and Node.js/npm so subprocesses spawned by PHP (e.g.composer create-project,npm install) resolve correctly inside the containercomposerandlaravelglobal tools found inside container —lerd phpnow passes the correctHOMEandCOMPOSER_HOMEenv vars and includes the Composer global bin dir in PATH so globally installed tools like the Laravel installer are found- Node/npm/npx shims work inside containers — shims now use
fnmdirectly (statically linked, works in Alpine) instead of callinglerd(glibc binary, incompatible with Alpine musl) - Shims use absolute paths —
php,composer,node,npm,npxshims now reference their binaries by absolute path, eliminating PATH-dependent failures in subprocess contexts
0.5.4 — 2026-03-19¶
Added¶
- Custom services: users can now define arbitrary OCI-based services without recompiling. Config lives at
~/.config/lerd/services/<name>.yaml. lerd service add [file.yaml]— add from a YAML file or inline flags (--name,--image,--port,--env,--env-var,--data-dir,--detect-key,--detect-prefix,--init-exec,--init-container,--dashboard,--description)lerd service remove <name>— stop (if running), remove quadlet and config; data directory preservedlerd service list— shows built-in and custom services with a[custom]type columnlerd service start/stop— works for custom serviceslerd start/lerd stop— includes installed custom serviceslerd env— auto-detects custom services viaenv_detect, appliesenv_vars, runssite_init.execlerd status— includes custom services in the[Services]section- Web UI services tab — shows custom services with start/stop and dashboard link
- System tray — shows custom services (slot pool expanded from 7 to 20)
{{site}}/{{site_testing}}placeholders inenv_varsandsite_init.exec— substituted with the project site handle atlerd envtimesite_initYAML block — runs ash -ccommand inside the service container once per project whenlerd envdetects the service (for DB/collection creation, user setup, etc.)dashboardfield — shows an "Open" button in the web UI when the service is active; dashboard URLs for built-ins (Mailpit, MinIO, Meilisearch) moved from hardcoded JS to the API response- README simplified — now a slim landing page pointing to the docs site
- Docs updated —
docs/usage/services.mdextended with full custom services reference
Fixed¶
- Custom service data directory is now created automatically before starting
lerd service removenow checks unit status before stopping — skips stop if not running, and aborts removal if stop fails
0.5.3 — 2026-03-19¶
Fixed¶
- Tray not restarting after
lerd update:lerd installwas killing the tray withpkillbut only relaunching it whenlerd-tray.servicewas enabled. If the tray was started directly (lerd tray), it was killed and never restarted. Now tracks whether the tray was running before the kill and relaunches it directly when systemd is not managing it.
0.5.2 — 2026-03-19¶
Fixed¶
lerd db:createandlerd db:shellwere missing from the binary —cmd/lerd/main.gowas not staged in the v0.5.1 commit
0.5.1 — 2026-03-19¶
Added¶
lerd db:create [name]/lerd db create [name]: creates a database and a<name>_testingdatabase in one command. Name resolution: explicit argument →DB_DATABASEfrom.env→ project name (site registry or directory). Reports "already exists" instead of failing when a database is present. Available for both MySQL and PostgreSQL.lerd db:shell/lerd db shell: opens an interactive MySQL (mysql -uroot -plerd) or PostgreSQL (psql -U postgres) shell inside the service container, connecting to the project's database automatically. Replaces the need to runpodman exec --tty lerd-mysql mysql …manually.
Changed¶
lerd envnow creates a<name>_testingdatabase alongside the main project database when setting up MySQL or PostgreSQL. Both databases report "already exists" if they were previously created.
0.5.0 — 2026-03-19¶
Added¶
- System tray applet (
lerd tray): a desktop tray icon for KDE, GNOME (with AppIndicator extension), waybar, and other SNI-compatible environments. The applet detaches from the terminal automatically and pollshttp://127.0.0.1:7073every 5 seconds. Menu includes: - 🟢/🔴 overall running status with per-component nginx and DNS indicators
- Open Dashboard — opens the web UI
- Start / Stop Lerd toggle
- Services section — lists all active services with 🟢/🔴 status; clicking a service starts or stops it
- PHP section — lists all installed PHP versions; current global default is marked ✔; clicking switches the global default via
lerd use - Autostart at login toggle — enables or disables
lerd-autostart.service - Check for update — polls GitHub; if a newer version is found the item changes to "⬆ Update to vX.Y.Z" and clicking opens a terminal with a confirmation prompt before running
lerd update - Stop Lerd & Quit — runs
lerd stopthen exits the tray --monoflag forlerd tray: defaults totrue(white monochrome icon); pass--mono=falsefor the red colour iconlerd autostart tray enable/disable: registers/removeslerd-tray.serviceas a user systemd unit that starts the tray on graphical loginlerd startstarts the tray: iflerd-tray.serviceis enabled it is started via systemd; otherwise, if no tray process is already running,lerd trayis launched directlymake build-nogui: headless build (CGO_ENABLED=0 -tags nogui) for CI or servers;lerd trayreturns a clear error instead of failing to link
Changed¶
- Build now requires CGO and
libappindicator3(libappindicator-gtk3on Arch,libappindicator3-devon Debian/Ubuntu,libappindicator-gtk3-develon Fedora). Themake buildtarget setsCGO_ENABLED=1 -tags legacy_appindicatorautomatically. lerd-autostart.servicenow declaresAfter=graphical-session.targetso the tray (which needs a display) is available whenlerd startruns at login.- Web UI update flow: the "Update" button has been removed. When an update is available the UI now shows
vX.Y.Z available — run lerd update in a terminal. The/api/updateendpoint has been removed. This avoids silent failures caused bysudosteps inlerd installthat require a TTY. /api/statusnow includes aphp_defaultfield with the global default PHP version, used by the tray to mark the active version with ✔.
0.4.3 — 2026-03-19¶
Fixed¶
- DNS broken after install on Fedora (and other NM + systemd-resolved systems): the NetworkManager dispatcher script and
ConfigureResolver()were callingresolvectl domain $IFACE ~test, which caused systemd-resolved to mark the interface asDefault Route: no. This meant queries for anything outside.test(i.e. all internet DNS) had no route and were refused. Fixed by also passing~.as a routing domain in both places — the interface now handles.testspecifically via lerd's dnsmasq and remains the default route for all other queries. .testDNS fails after reboot/restart:lerd startwas callingresolvectl dnsto point systemd-resolved at lerd-dns (port 5300) immediately after the container unit became active — but dnsmasq inside the container wasn't ready to accept connections yet. systemd-resolved would try port 5300, fail, mark it as a bad server, and fall back to the upstream DNS for the rest of the session. Fixed by waiting up to 10 seconds for port 5300 to accept TCP connections before callingConfigureResolver().- Clicking a site URL after disabling HTTPS still opened the HTTPS version: the nginx HTTP→HTTPS redirect was a
301(permanent), which browsers cache indefinitely. After disabling HTTPS, the browser would serve the cached redirect instead of hitting the server. Changed to302(temporary) so browsers always check the server, and disabling HTTPS takes effect immediately.
0.4.2 — 2026-03-19¶
Changed¶
lerd setupdetects the correct asset build command frompackage.json: instead of always suggestingnpm run build, the setup step now readsscriptsfrompackage.jsonand picks the first available candidate in priority order:build(Vite / default),production(Laravel Mix),prod. The step label reflects the detected command (e.g.npm run production). If none of the candidates exist, the build step is omitted from the selector.
0.4.1 — 2026-03-19¶
Fixed¶
lerd statusTLS certificate check:certExpirywas passing raw PEM bytes directly tox509.ParseCertificate, which expects DER-encoded bytes. The fix decodes the PEM block first, so certificate expiry is read correctly and sites no longer show "cannot read cert" when the cert file exists and is valid.
0.4.0 — 2026-03-19¶
Added¶
- Xdebug toggle (
lerd xdebug on/off [version]): enables or disables Xdebug per PHP version by rebuilding the FPM image with Xdebug installed and configured (mode=debug,start_with_request=yes,client_host=host.containers.internal, port 9003). The FPM container is restarted automatically.lerd xdebug statusshows enabled/disabled for all installed versions. lerd fetch [version...]: pre-builds PHP FPM images for the specified versions (or all supported: 8.1–8.5) so the firstlerd use <version>is instant. Skips versions whose images already exist.lerd db:import <file.sql>/lerd db:export [-o file]: import or export a SQL dump using the project's.envDB settings. Supports MySQL/MariaDB (lerd-mysql) and PostgreSQL (lerd-postgres). Also available aslerd db import/lerd db export.lerd share [site]: exposes the current site publicly via ngrok or Expose. Auto-detects which tunnel tool is installed; use--ngrokor--exposeto force one. Forwards to the local nginx port with the correctHostheader so nginx routes to the right vhost.lerd setup: interactive project bootstrap command — presents a checkbox list of steps (composer install, npm ci, lerd env, lerd mcp:inject, php artisan migrate, php artisan db:seed, npm run build, lerd secure, lerd open) with smart defaults based on project state.lerd linkalways runs first (mandatory, not in the list) to ensure the site is registered with the correct PHP version before any subsequent step.--all/-aruns everything without prompting (CI-friendly);--skip-openskips opening the browser.
Fixed¶
- PHP version detection order:
composer.jsonrequire.phpnow takes priority over.php-version, so projects declaring"php": "^8.4"incomposer.jsonautomatically use PHP 8.4 even if a stale.php-versionfile says otherwise. Explicit.lerd.yamloverrides still take top priority. lerd linkpreserves HTTPS: re-linking a site that was already secured now regenerates the SSL vhost (not an HTTP vhost), sohttps://continues to work after a re-link.lerd linkpreservessecuredflag: re-linking no longer resets a secured site tosecured: false.lerd secure/lerd unsecuredirectory name resolution: sites in directories with real TLDs (e.g.astrolov.com) are now resolved correctly by path lookup, so the commands no longer error with "site not found" when the directory name differs from the registered site name.
0.3.0 — 2026-03-18¶
Added¶
lerd envcommand: copies.env.example→.envif missing, detects which services the project uses, applies lerd connection values, starts required services, generatesAPP_KEYif missing, and setsAPP_URLto the registered.testdomainlerd unsecure [name]command: removes the mkcert TLS cert and reverts the site to HTTPlerd secureandlerd unsecurenow automatically updateAPP_URLin the project's.envtohttps://orhttp://respectivelylerd installnow installs a/etc/sudoers.d/lerdrule granting passwordlessresolvectl dns/domain/revert— required for the autostart service which cannot prompt for a sudo password- PHP FPM images now include the
gmpextension - MCP server (
lerd mcp): JSON-RPC 2.0 stdio server exposing lerd as a Model Context Protocol tool provider for AI assistants (Claude Code, JetBrains Junie, and any MCP-compatible client). Tools:artisan,sites,service_start,service_stop,queue_start,queue_stop,logs lerd mcp:inject: writes.mcp.json,.claude/skills/lerd/SKILL.md, and.junie/mcp/mcp.jsoninto a project directory. Merges into existingmcpServersconfigs — other servers (e.g.laravel-boost,herd) are preserved unchanged- UI: queue worker toggle in the Sites tab — amber toggle to start/stop the queue worker per site; spinner while toggling; error text on failure; logs link opens the live log drawer for that worker when running
- UI: Unlink button in the Sites tab — small red-bordered button that confirms, calls
POST /api/sites/{domain}/unlink, and removes the site from the table client-side immediately lerd unlinkparked-site behaviour: unlinking a site under a parked directory now marks it asignoredin the registry instead of removing it, preventing the watcher from re-registering it on next scan. Runninglerd linkin the same directory clears the flag. Non-parked sites are still removed from the registry entirelyGET /api/sitesfilters out ignored sites so they are invisible in the UIqueue:startandqueue:stopare now also available as API actions viaPOST /api/sites/{domain}/queue:startandPOST /api/sites/{domain}/queue:stop, enabling UI and MCP control
Fixed¶
- DNS
.testrouting now works correctly after autostart:resolvectl revertis called before re-applying per-interface DNS settings so systemd-resolved resets the current server to127.0.0.1:5300; previously, resolved would mark lerd-dns as failed during boot (before it started) then fall back to the upstream DNS for all queries including.test, causing NXDOMAIN on every.testlookup fnm installno longer prints noise to the terminal when a Node version is already installed
Changed¶
lerd startandlerd stopnow start/stop containers in parallel — startup is noticeably faster on multi-container setupslerd startnow re-applies DNS resolver config on every invocation, ensuring.testrouting is always correct after reboot or network changeslerd parknow skips already-registered sites instead of overwriting them, preserving settings such as TLS status and custom PHP versionlerd installcompletion message now shows bothhttp://lerd.testandhttp://127.0.0.1:7073as fallback- Composer is now stored as
composer.phar; thecomposershim runs it vialerd php - Autostart service now declares
After=network-online.targetand runs at elevated priority (Nice=-10)
0.2.0 — 2026-03-17¶
Changed¶
- UI completely redesigned: dark theme inspired by Laravel.com with near-black background, red accents, and top navbar replacing the sidebar
- Light / Auto / Dark theme toggle added to the navbar; preference persists in localStorage
0.1.0 — 2026-03-17¶
Initial release.
Added¶
Core - Single static Go binary built with Cobra - XDG-compliant config (~/.config/lerd/) and data (~/.local/share/lerd/) directories - Global config at ~/.config/lerd/config.yaml with sensible defaults - Per-project .lerd.yaml override support - Linux distro detection (Arch, Debian/Ubuntu, Fedora, openSUSE) - Build metadata injected at compile time: version, commit SHA, build date
Site management - lerd park [dir] — auto-discover and register all Laravel projects in a directory - lerd link [name] — register the current directory as a named site - lerd unlink — remove a site and clean up its vhost - lerd sites — tabular view of all registered sites
PHP - lerd install — one-time setup: directories, Podman network, binary downloads, DNS, nginx - lerd use <version> — set the global PHP version - lerd isolate <version> — pin PHP version per-project via .php-version - lerd php:list — list installed static PHP binaries - PHP version resolution order: .php-version → .lerd.yaml → composer.json → global default
Node - lerd isolate:node <version> — pin Node version per-project via .node-version - Node version resolution order: .nvmrc → .node-version → package.json engines.node → global default - fnm bundled for Node version management
TLS - lerd secure [name] — issue a locally-trusted mkcert certificate for a site - Automatic HTTPS vhost generation - mkcert CA installed into system trust store on lerd install
Services - lerd service start|stop|restart|status|list — manage optional services - Bundled services: MySQL 8.0, Redis 7, PostgreSQL 16, Meilisearch v1.7, MinIO
Infrastructure - All containers run rootless on a dedicated lerd Podman network - Nginx and PHP-FPM as Podman Quadlet containers (auto-managed by systemd) - dnsmasq container for .test TLD resolution via NetworkManager - fsnotify-based watcher daemon (lerd-watcher.service) for auto-discovery of new projects
Diagnostics - lerd status — health overview: DNS, nginx, PHP-FPM containers, services, cert expiry - lerd dns:check — verify .test resolution
Lifecycle - lerd update — self-update from latest GitHub release (atomic binary swap) - lerd uninstall — stop all containers, remove units, binary, PATH entry, optionally data - Shell completion via lerd completion bash|zsh|fish