The per-request signal lerd already carried, once starved down to a bare hostname for idle-suspend, becomes a full request-timing analytics view for every site, with slow-route detection, cold-start exclusion, and one-click profiling. Service presets follow frameworks out of the binary into an external store that updates without a release. And the whole thing joins up for agents: route_timing finds the slow route, optimize_route hands back the N+1 queries and CPU hotspots behind it, from real traffic rather than reading code. Alongside, certificates renew themselves, DNS toggles through dedicated commands, database client tools become host shims, and the dashboard grows a QR code and a mobile session.
The nginx access feed was widened from the bare request host it fed idle-suspend to carry status, request time, method and URI, and a framework-agnostic aggregator in the watcher keeps a rolling per-site, per-route window off that same syslog feed, running whether or not idle-suspend is on. The site Overview gains a Request timing section backed by a durable SQLite record: a range picker, KPIs for the typical and p95 response, request count and error rate, a response-time distribution, a throughput chart, the slowest routes, and routes and recent-requests panels.
A durable store drives the range picker, the typical and p95 KPIs, the distribution, a live throughput series, and the slowest routes with click-to-profile.
internal/reqstatsThe first request after a site sits idle past the suspend timeout is recorded with a cold flag and kept out of every timing figure, while still counting toward the total and showing in the recent list with a marker.
A slow route surfaces as a Response Time warning in the site doctor that the CLI, dashboard, and MCP all inherit, and as an opt-in slow_route push that fires once and rearms when the route recovers.
The timing signal is only half the story. An agent asking "why is this route slow?" wants the symptom and its cause in one call, without reading controllers to guess at N+1s. route_timing returns the response-time table from real traffic, and optimize_route joins each slow route to the queries and the CPU behind it. A new spxreport package parses SPX's on-disk trace into a flat profile and keeps only the outliers, so a CPU-bound route shows where its time went next to its queries.
the per-site response-time table, the routes whose p95 runs well above the site's typical, read from the snapshot with no capture needed #726
dumps_toggle for queries and profiler_toggle for SPX, then a few real hits, an authenticated POST just works, no synthetic request to forge
each slow route paired with its N+1 and slow queries at file:line, plus a profile block of the top functions by exclusive wall time from the freshest capture #728
lerd profile run --flat and profiler_report already return for a command, now in a route-scoped, historical form from passive traffic.route_timing(site: "shop") # which routes are slow dumps_toggle(enable) · profiler_toggle(enable) # … hit GET /orders a few times … optimize_route(site: "shop", route: "/orders") → N+1: OrderController@index:42 eager-load `items` → CPU: CurrencyFormatter::format 61% excl. wall
Service presets adopt the same fetch-and-cache model frameworks already use. The binary now embeds only the default stack, mysql, redis, postgres, meilisearch, rustfs, mailpit, and every add-on lives in the lerd-env/services store, fetched on demand into a local cache, refreshed after 24 hours, with the embedded defaults kept as an offline fallback so older installs keep resolving. A new service ships to every install without a binary release, which is the whole point.
lerd service search browses the store, lerd service preset <name> fetches a store-only preset on demand, and a reconcile backfill on start pulls the definitions of already-installed add-ons so their file mounts, families and dashboards survive the move out of the binary.
The preset picker filters live by name, description or image with an empty state, and every installable preset is promoted into the global command palette under an Install service group, so "install redis" installs it without opening the picker. Service tools render as cards #765.
The client tools that ship inside each database service image, mysqldump, pg_dump, psql, redis-cli, mongodump and friends, are surfaced as pass-through shims on the host PATH. Dump or query a database that lives outside lerd, or point PhpStorm at a real client binary, without installing the tool yourself. Each shim runs the tool in a throwaway container built from the service image with your home directory mounted, so output files and CA certs behave like a native binary and the running database container is never touched.
lerd shims # list every exposed client tool lerd shims add psql # put psql on the host PATH pg_dump mydb > dump.sql # runs in a throwaway container
Two long-standing sharp edges, rounded off. A secured site used to serve its mkcert leaf until it expired and then keep serving the expired one, and switching lerd-managed DNS on or off meant re-running the whole installer. Certificates now renew themselves, and DNS gets three dedicated commands.
IssueCert is now expiry-aware: it keeps an existing certificate only while it parses, is valid, and sits more than thirty days from expiry, otherwise it reissues. The watcher boot scan runs a new EnsureCert for secured sites so an ordinary lerd start heals an aging cert, covering the site's own domains and every worktree wildcard SAN.
lerd secure --renew and MCP tls_renew for an on-demand reissueThe install-time DNS question is asked once, on a genuine first install, and every later install or update honours the saved choice silently, the same way the Node-management choice does.
dns:enable turns lerd-managed DNS ondns:disable moves sites to localhostdns:repair re-runs setup to fix a broken but enabled resolverTwo power-user gaps closed. Nothing you started on a raw TCP port inside lerd shell used to be reachable from the host, and filling in a project's missing .env keys meant hunting for where each one belonged.
Each PHP version carries its own list of extra host ports, published on that version's shared FPM container so localhost:PORT reaches a Vite dev server, a websocket, or an ad-hoc listener you start in the shell. A requested port already claimed shifts to the next free one, and changing a version's list restarts only that version's FPM.
lerd php:ports add|remove|listports_list / ports_add / ports_removeAn anchor-based merge places each key the project's .env is missing next to the neighbours it has in .env.example, carrying its inline comment and value verbatim, purely additively. lerd env:check --fix shows a unified diff; the dashboard Env tab opens a Review keys modal #735 listing each missing key with its value and a checkbox, required keys pre-ticked.
Cleanup only reaped provably-lerd orphaned images by default, so upgraded service versions and the dangling leftovers repeated rebuilds pile up went untouched unless someone remembered --deep. On a long-lived install that let podman grow tens of gigabytes while both lerd cleanup and the daily sweep reported nothing to do. Deep is now the default #740, with a --safe flag to drop back to the conservative pass.
Nearly a third of the release lands in the web UI. The dashboard learns to be driven from a phone over a shared LAN, the site page is rebuilt as a resource view, and every tooltip is rewritten to escape the panels that used to clip it.
The Remote dashboard card shows the http://<lan-ip>:7073 address next to a scannable QR. A first Basic-auth success sets an HttpOnly, HMAC-signed lerd_session cookie valid for a week, so iOS Safari stops re-prompting on every refresh; rotating the credentials invalidates every session.
Toolbars wrap, the framework badge hides on narrow screens, the open action targets the LAN share URL where .test won't resolve, Tinker no longer zooms on iOS focus, and loopback-only launchers drop out of a remote dashboard.
The Overview becomes a stack of sections, a runtime-and-workers row with the doctor and framework commands as a matched pair, a service-card grid where each running service opens its dashboard, and live logs on their own tab. The project path opens the folder in the host file manager.
A shared use:tooltip action renders one themed bubble portaled to the body, so tooltips escape the panels and scroll containers that clipped the native title labels, follow the light or dark theme, and clamp to the viewport. Every trigger keeps an aria-label.
A tune_command template with {queue}/{tries}/{timeout} and a restart_command on the framework worker replace the hardcoded php artisan queue:work, so CodeIgniter's php spark queue:work and any non-Laravel framework declare their own flag syntax. Detection also resolves offline from the cached store index #776.
Fixes that landed against previously shipped code, plus the org move. Correctness fixes to features born in this cycle are folded into their chapters above, not listed here.
| Fix | What changed | PR |
|---|---|---|
| AI config pollution | mcp:inject stops writing lerd's block into a shared, committed AGENTS.md or guidelines file that belongs to the project | #795 |
| Install idempotency | the DNS sudoers rule is no longer rewritten on every install, prompts stop swallowing typed-ahead input, and podman-mac-helper's sudo prompt is skipped when already installed | #731 #759 #761 |
| Host-worker consent | keyed on the command that actually runs, so the v1.27 gate remembers the approval instead of re-prompting | #772 |
| MCP + UI feedback | podman hot-path calls are bounded and a stalled machine self-heals; the check-for-updates button gives live feedback | #716 #787 |
| macOS heal | the machine heal, DNS teardown, and CA trust check are hardened | #791 |
| v1.27 review | correctness, macOS-compatibility and safety follow-ups from reviewing the v1.27 changes | #763 #773 #799 #802 |
Under the hood, distribution and CI moved from geodro/lerd to the lerd-env organisation #757, Go dependencies were updated including the x/net DoS fix #767, and the repo gained a CLAUDE.md agent guide with contributor-agent skills #722.