Grove · · 5 min read

Growing Grove: a cozy little dev environment in Rust

Why I built my own Valet/Herd in Rust — and how it ended up downloading its own databases. Zero external dependencies: embedded DNS, a reverse proxy, a local CA, self-contained PHP builds, and services that install themselves.

Growing Grove: a cozy little dev environment in Rust

There's a particular kind of friday-evening restlessness where you don't want to finish anything — you just want to tinker. That's how Grove started. Not as a grand plan, but as a small itch: every time I set up a new Mac, I'd lose an afternoon to Homebrew, Composer, dnsmasq, a PHP version dance, and a *.test domain that worked on one machine and sulked on another.

I love Laravel Valet. I've paid for Herd. But I kept bumping into the same three walls:

  • Valet is elegant and feather-light — and macOS-only, leaning on a pile of Homebrew packages.

  • Herd ships clean static binaries — but it's closed, won't let me load my own PHP extensions, and tucks databases and mail behind a Pro license.

  • Docker is wonderful and also way too much machinery just to open blog.test.

So one evening I asked the dangerous question: how hard could it be?

The answer, pleasantly, was: hard enough to be fun, easy enough to finish.

The why: own the whole stack, depend on nothing

The guiding rule for Grove became almost stubborn:

Zero external dependencies. If Grove needs it, Grove ships it.

No Homebrew. No system dnsmasq. No OpenSSL. Not even PHP needs to be on your machine. That sounds extreme until you realize it's also kind — to your future self on a fresh laptop, and to teammates on Linux and Windows who just want the same thing to work.

In practice that meant building the boring-but-load-bearing pieces directly into a Rust core:

  • an embedded DNS resolver that answers *.test with loopback (and politely refuses everything else),

  • an HTTP/HTTPS reverse proxy that routes by Host header,

  • a tiny FastCGI client to talk to PHP-FPM,

  • a local certificate authority that mints per-site HTTPS certs on demand.

No nginx, no mkcert, no vendor of system tools. Just one daemon.

The how: one daemon, two thin clients

The shape is simple. A single long-running daemon binds the privileged ports (53, 80, 443) and supervises everything. The CLI and the GUI are both thin clients that talk to it over a local socket. They're always in parity because they speak the exact same JSON-RPC.

From a clean machine to a running site looks like this:

sudo grove init        # config, root CA, a static PHP build, resolver + trust
grove start            # the daemon binds 53/80/443
grove park ~/Code      # every subfolder becomes <name>.test

That's it. Open https://blog.test and it's there — HTTPS padlock and all — without ever editing /etc/hosts.

$ grove list
SITE                     DRIVER     PHP   NODE  HTTPS  URL
blog.test                static     8.4   —     no     http://blog.test
inside-next.test         laravel    8.4   22    yes    https://inside-next.test
frontend.test            proxy      —     —     no     http://frontend.test

Want HTTPS and a pinned PHP version for one project? One line each:

grove secure inside-next
grove isolate inside-next 8.3

The part that surprised me: it grew

Here's where the project stopped being "a router" and started being a little ecosystem.

PHP, plural. Grove downloads self-contained static PHP-FPM builds — not one, but as many as you like:

$ grove php install 8.5
  resolving latest 8.5 for macos-aarch64…
  downloading php-8.5.7-fpm-macos-aarch64.tar.gz…
  installed: PHP 8.5.7 (fpm-fcgi)

otool -L on that binary shows only macOS's own system libraries. Nothing from Homebrew. It just runs.

Databases that install themselves. This was the line I didn't want to cross — and then crossed happily. You shouldn't have to brew install mysql to use Grove. So Grove fetches and supervises them itself:

$ grove service install postgres && grove service start postgres
$ grove service install redis    && grove service start redis
$ grove service list
SERVICE      CATEGORY       INSTALLED  RUNNING   PORT
PostgreSQL   Database       yes        yes       5432
MySQL        Database       no         no        3306
Redis        Cache & Queue  yes        yes       6379

PostgreSQL and MySQL come as portable binaries; Redis is compiled from source on install (it's famously dependency-light). Then — my favorite small touch — Grove writes the .env for you:

$ grove env shop
# Generated by `grove env` — Grove's bundled services
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=shop
DB_USERNAME=root
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1025

New projects, from scratch. And because we're already here:

$ grove new shop --kind laravel
  preparing PHP CLI…
  preparing Composer…
  creating Laravel project (composer)…
  ✓ created shop at ~/Code/shop

Bundled PHP CLI, bundled Composer, a fresh Laravel app, auto-detected and served at shop.test. No global installs.

A face for it

The CLI is the engine, but a good dev tool should also be nice to look at. So Grove has a Tauri + Svelte GUI sharing the Tokyo Night palette of the rest of my toolkit — a dashboard of sites, one-click HTTPS toggles, per-site PHP/Node dropdowns, a Mailpit-style mail viewer, a log reader, and panels to install PHP/Node and start databases.

It lives in the macOS menu bar, too. Close the window and Grove tucks itself up by the clock; click the little orange node-graph and it's back. And from the first proper release onward, it quietly checks for updates and offers a one-click Install & restart — cryptographically signed, of course.

What I learned

Three small lessons stuck with me:

  1. "Zero dependencies" is a feature you feel. The moment grove init set up a working .test site with HTTPS on a machine with nothing installed, the whole project justified itself.

  2. Bundling is mostly logistics. Downloading a portable Postgres or compiling Redis isn't magic — it's a catalog, a tarball, an initdb, and a supervised child process. The trick is doing it consistently.

  3. The blank white screen will humble you. (Ask me how long I stared at an empty Tauri window before realizing the webview was politely trying to reach a dev server that didn't exist.)

Grove is v0.1.1 today — macOS-first, Linux and Windows on the way. It's not trying to replace Docker for your microservice opera. It's the small, warm thing that gets a *.test site running in under five minutes and then gets out of your way.

If that sounds like your kind of friday evening, the grove is open. 🌳

$ grove gui
✓ Grove GUI launched