Installing Grove
This is the complete, step-by-step guide to installing Elyra Grove and
serving your first *.test site with local HTTPS.
Grove is a single, self-contained Rust daemon with a thin CLI and a desktop GUI. It has zero external dependencies — it downloads and manages its own PHP, Node, PostgreSQL, MySQL and Redis. You do not need Homebrew, Composer, dnsmasq, nvm or anything else.
- 1. Requirements
- 2. Install the app
- 3. First-run setup
- 4. Install the background service
- 5. Verify it works
- 6. Serve your first site
- 7. Enable HTTPS and pin a PHP version
- 8. The desktop GUI
- 9. Bundled databases & services
- 10. PHP & Node versions
- 11. Mail, logs & diagnostics
- 12. Troubleshooting
- 13. Updating
- 14. Uninstalling
1. Requirements
| OS | macOS 12 (Monterey) or newer |
| Architecture | Apple Silicon (arm64) or Intel (x86_64) |
| Privileges | An admin account (you will run sudo once during setup) |
| Disk | ~300 MB for the app + one PHP build; more as you add runtimes |
Grove needs the privileged ports 53 (DNS), 80 (HTTP) and 443 (HTTPS). The installer sets up a small background service that owns those ports for you — see step 4.
2. Install the app
- Download the latest
Grove_<version>_aarch64.dmg(Apple Silicon) or…_x64.dmg(Intel) from the releases page. - Open the
.dmgand drag Grove into Applications. - Launch Grove from Applications.
The app is code-signed with a Developer ID and notarized by Apple, so it
opens normally — no Gatekeeper warning, no xattr workarounds.
Make the grove command available (recommended)
The CLI ships inside the app. Symlink it onto your PATH so you can type
grove anywhere:
sudo ln -sf "/Applications/Grove.app/Contents/MacOS/grove" /usr/local/bin/grove
Password:
Verify:
grove --version
grove 0.1.5
Every example below uses
grove …. If you skip the symlink, replacegrovewith the full path/Applications/Grove.app/Contents/MacOS/grove.
3. First-run setup
Run the one-time setup. This creates the config, generates a local root
Certificate Authority (for HTTPS), installs a static PHP build, and registers
the macOS DNS resolver for .test.
sudo grove init
Password:
✓ config created at /Users/you/Library/Application Support/Grove/config.toml
✓ root CA generated at …/Grove/certs/grove-ca.pem
✓ root CA added to the system trust store
✓ PHP 8.4 downloaded and registered
✓ resolver /etc/resolver/test → 127.0.0.1:53
init complete — next run: sudo grove install
Why
sudo? Trusting the CA and writing/etc/resolver/testrequire administrator rights. Grove drops back to your user for everything else, so your files stay owned by you.
4. Install the background service
Install Grove as a system service. It runs in the background, binds ports 53/80/443, starts automatically at boot, and restarts if it ever crashes. PHP itself still runs as your user, not root.
sudo grove install
Password:
✓ service installed: /Library/LaunchDaemons/com.elyra.grove.plist (runs at boot, binds the ports, resolver ensured)
That's it — Grove is now running. You never need sudo grove start again.
A harmless
Boot-out failed: 5: Input/output errorline may appear before the success message. That is just Grove cleaning up a service that wasn't loaded yet — you can ignore it.
5. Verify it works
Check the daemon and environment:
grove status
Grove 0.1.5
TLD .test
HTTP :80
HTTPS :443
DNS :53
Sites 0
● dns
● mail
Run diagnostics:
grove doctor
✓ config loaded from /Users/you/Library/Application Support/Grove/config.toml
✓ root-ca present at …/Grove/certs/grove-ca.pem
✓ privileges http_port=80, elevated=true
✓ resolver /etc/resolver/test present
✓ dns 127.0.0.1:53 answering
Confirm DNS resolution goes through Grove:
dig +short whatever.test
127.0.0.1
If you see 127.0.0.1, the system is correctly routing *.test to Grove. 🎉
6. Serve your first site
You have two ways to expose projects.
Option A — Park a whole folder
Point Grove at a directory; every subdirectory becomes <name>.test.
grove park ~/Code
✓ parked ~/Code — 12 sites now resolve as <name>.test
A project at ~/Code/blog is instantly available at http://blog.test.
Option B — Link a single project
From inside a project directory:
cd ~/Code/blog
grove link
✓ linked blog → http://blog.test
List everything Grove serves:
grove list
SITE DRIVER PHP HTTPS URL
blog.test laravel 8.4 no http://blog.test
shop.test laravel 8.4 no http://shop.test
docs.test static 8.4 no http://docs.test
Grove auto-detects the right driver (Laravel, WordPress, plain PHP, static, or a reverse proxy) from each project's contents.
7. Enable HTTPS and pin a PHP version
Turn on local TLS for a site (served from Grove's trusted CA):
grove secure blog
✓ blog is now served over HTTPS → https://blog.test
Open https://blog.test — a valid padlock, no warnings.
Pin a specific PHP version for one site without affecting the others:
grove isolate blog 8.3
✓ blog isolated to PHP 8.3
Revert when you're done:
grove unisolate blog
grove unsecure blog
Create a brand-new project
grove new myapp --laravel
✓ scaffolded a fresh Laravel app at ~/Code/myapp
✓ linked myapp → http://myapp.test
8. The desktop GUI
Launch Grove from Applications for a dashboard over the same daemon:
- Sites — view, secure, isolate and open every site
- Services — start/stop bundled databases and caches
- Mail — read captured outgoing email
- PHP / Node — install and switch runtime versions
- Logs — tail daemon, site and service logs
- Doctor — run diagnostics from the UI
The status pill (top-right) shows ● Running once it connects to the background service.
The GUI is just a client. Don't use a "Start" button to launch a second daemon — the installed background service already owns the ports. If the GUI shows Stopped while sites clearly work, see Troubleshooting.
9. Bundled databases & services
Grove installs and supervises its own PostgreSQL, MySQL and Redis — no Homebrew.
grove service list
SERVICE CATEGORY INSTALLED RUNNING PORT
PostgreSQL Database no no 5432
MySQL Database no no 3306
Redis Cache & Queue no no 6379
Install and start one:
grove service install postgres
grove service start postgres
✓ PostgreSQL installed (16.x)
✓ PostgreSQL running on :5432
Print a ready-made .env block wiring an app to Grove's services:
grove env
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=grove
DB_USERNAME=grove
DB_PASSWORD=
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1025
10. PHP & Node versions
List, install and switch PHP:
grove php list
php@8.4 → …/Grove/runtimes/8.4/php-fpm
grove php install 8.3
grove use 8.3 # set the global default
Node works the same way:
grove node install 22
grove node use 22
✓ Node 22.x installed
✓ default Node set to 22
11. Mail, logs & diagnostics
Grove runs a built-in mail-catcher on 127.0.0.1:1025. Anything your apps send
is captured (never delivered) and viewable:
grove mail
# FROM TO SUBJECT RECEIVED
1 hello@blog.test you@example.com Welcome aboard! 12:04:31
List and tail logs:
grove logs
grove logs daemon
available logs: daemon, dns, mail, blog, shop
12. Troubleshooting
DNS_PROBE_FINISHED_NXDOMAIN / ERR_NAME_NOT_RESOLVED in the browser
The browser isn't routing .test to Grove. Re-create the resolver and flush the
DNS cache:
sudo mkdir -p /etc/resolver
printf 'nameserver 127.0.0.1\nport 53\n' | sudo tee /etc/resolver/test
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
nameserver 127.0.0.1
port 53
Confirm:
dig +short blog.test # must print 127.0.0.1
If it still fails, disable Chrome → Settings → Privacy → Use secure DNS. "Secure DNS" (DoH) bypasses
/etc/resolverand sends.testto a public resolver that doesn't know your domains.From v0.1.5,
sudo grove installre-creates this resolver automatically, so re-running it also fixes the problem.
Address already in use / ports 80/443 won't bind
Another daemon (often a leftover sudo grove start) is holding the ports.
Restart the service cleanly:
sudo launchctl bootout system/com.elyra.grove 2>/dev/null
sudo pkill -f "grove daemon" 2>/dev/null
sleep 2
sudo launchctl bootstrap system /Library/LaunchDaemons/com.elyra.grove.plist
Check who is listening:
sudo lsof -nP -iTCP:80 -iTCP:443 | grep LISTEN
GUI shows "Stopped" but sites work
This was fixed in v0.1.5 (the GUI and daemon now share the exact same home directory). Update the app to v0.1.5 or newer. As an immediate workaround on older builds:
cd "$HOME/Library/Application Support"
rm -rf "com.elyra.Grove"
ln -s "Grove" "com.elyra.Grove"
The GUI re-checks every few seconds and will flip to ● Running.
"Grove is damaged and can't be opened"
Only happens on unsigned builds. The official .dmg is notarized. If you built
from source yourself:
xattr -dr com.apple.quarantine /Applications/Grove.app
Inspect the daemon's own logs
tail -f "$HOME/Library/Application Support/Grove/daemon.out.log"
tail -f "$HOME/Library/Application Support/Grove/daemon.err.log"
13. Updating
The app updates itself: when a new signed release is published, Grove shows an in-app banner — click Update and relaunch.
To update the CLI symlink target, nothing is needed; it always points at the installed app bundle.
14. Uninstalling
Remove the background service, the DNS resolver and the CA trust:
sudo grove uninstall
Password:
✓ service removed
✓ resolver removed (/etc/resolver/test)
✓ root CA untrusted
Then drag Grove from Applications to the Trash, remove the symlink, and (if you want a clean slate) delete the state directory:
sudo rm -f /usr/local/bin/grove
rm -rf "$HOME/Library/Application Support/Grove"
Questions or problems? Open an issue at https://github.com/kwhorne/grove/issues or start a discussion at https://github.com/kwhorne/grove/discussions.