Elyra
Elyra The coding agent e The native code editor Elyra Grove Native local development environment Elyra Conductor Local project conductor Elyra SQL Anywhere Replication-ready SQL engine
Internals
Architecture
Release notes
Changelog
Elyra
Installing Grove

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

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

  1. Download the latest Grove_<version>_aarch64.dmg (Apple Silicon) or …_x64.dmg (Intel) from the releases page.
  2. Open the .dmg and drag Grove into Applications.
  3. 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, replace grove with 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/test require 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 error line 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/resolver and sends .test to a public resolver that doesn't know your domains.

From v0.1.5, sudo grove install re-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.