Skip to content

Liber Tertius · Chapter 1 · Getting grim

Run grim projects beside Laravel Herd

Herd and Traefik both want ports 80 and 443, and for a long time grim's answer was to stop Herd. Now the machine can be told that Herd is in front. Traefik moves to a loopback port, Herd proxies each project's names to it, and Herd's own certificate authority signs them.

≈15 min 4 steps Laravel Herd running with its tld set to test Docker Desktop MySQL on 127.0.0.1 that root can open without a password
Source

Before you start

Herd's nginx listens on 127.0.0.1:80 and :443. grim's Traefik binds 0.0.0.0:80 and :443. They cannot both hold the ports, and grim used to win by running herd stop. This recipe is the other arrangement: Herd keeps the ports, and grim runs behind it.

Two things follow from that. Projects behind Herd are reachable at *.test only, because Herd appends .test to any proxy it writes; a project with a tls block of mode custom cannot run this way. And the mode belongs to the machine, not to a project: every project on the Mac goes through the one Traefik, so grim config holds the setting, not grim.json.

Check that Herd is what answers .test and that its tld is test:

$ herd tld
test
$ herd links

MySQL is unchanged. grim projects reach it on the host, and Herd's own services are not needed.

The ritual, in order

  1. Tell the machine that Herd is in front

    $ grim config set proxy.mode behind-herd
    proxy.mode = behind-herd

    The port Traefik binds defaults to 8080 on the loopback address. When something else holds it, pick another: grim config set proxy.port 8123.

  2. Set up Traefik behind Herd

    $ grim setup
    Setting up local dev environment...
    
      Creating traefik-public network... OK
      Checking mkcert... installed
      Installing mkcert CA... OK
      SSL certs will be generated per-project on grim up
      Setting up Traefik... running (behind-herd, 127.0.0.1:8080)
      Setting up *.test DNS... already configured
    
    Local dev environment ready.

    Traefik now listens on 127.0.0.1:8080 only, with no certificates. Herd is not touched. A Traefik that was already running on 80 and 443 is recreated on the new port, and the line says so.

    $ curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: nothing.test' http://127.0.0.1:8080/
    404

    The 404 is Traefik's: it answers, and knows no project yet.

  3. Raise a project

    In a project directory, or with a new one from grim create-project:

    $ grim up
      ✓ Herd proxy shop.test → http://127.0.0.1:8080 (written)
      ✓ Herd proxy mail.shop.test → http://127.0.0.1:8080 (written)
    
    Starting: shop
      ✓ Checking Traefik
      ✓ Running docker compose up
      …
      ✓ Verifying Traefik on 127.0.0.1:8080
    
      ✓ Herd serves https://shop.test (200)

    The first proxy on a machine asks for your password: Herd installs its certificate authority into the system keychain once. From then on the two proxies are checked on every grim up and rewritten only when they are missing or point elsewhere. There is no mkcert certificate and no TLS chain check in this mode; Herd terminates TLS, and Vite's dev server reads Herd's certificate for the domain.

    Open https://shop.test and https://mail.shop.test. Herd's other sites are still up.

  4. Put it away

    grim down stops the containers and leaves the proxies: Herd answers 502 for the names until the next grim up. To forget the project on this machine, remove the proxies as well:

    $ grim down --purge
      ✓ Herd proxy shop.test removed
      ✓ Herd proxy mail.shop.test removed

What you have now

  • Traefik on 127.0.0.1:8080, routing by Host header, trusting Herd's X-Forwarded-* headers.
  • Two Herd proxies per project, <name>.test and mail.<name>.test, secured by Herd's authority.
  • grim doctor shows Proxy mode ✓ behind-herd (127.0.0.1:8080) and, inside a project, Herd proxies ✓.
  • Herd's own sites, untouched.

To go back, grim config set proxy.mode traefik and grim setup --stop-herd: Traefik returns to 80 and 443 and Herd has to be stopped for that.

When it does not work

Herd answers 502

Traefik or the project is down. grim up in the project. grim doctor says which proxy is stale and where Traefik binds.

Nothing answers on shop.test at all

Herd's nginx is not listening. After grim had stopped it, herd start reports the services started and yet leaves nginx down; herd restart brings it back. lsof -nP -iTCP:443 -sTCP:LISTEN should show nginx-arm64 on 127.0.0.1:443.

The page is a 500

The request went through: Herd, Traefik and the project's nginx all answered, and the application failed. That is the same 500 you would get on port 8080 directly (curl -H 'Host: shop.test' http://127.0.0.1:8080/). Read grim logs app and storage/logs/laravel.log.

The browser does not trust shop.test

Herd's authority was not installed, usually because the password prompt was dismissed. herd secure shop asks again.

“… which behind-herd cannot serve”

The project has a tls block with a custom domain, or GRIM_LOCAL_DOMAIN in .env. Behind Herd only *.test is served. Remove the block or the key for this machine, or switch the machine back to proxy.mode traefik.

Running without Docker

Not this recipe. grim install --no-docker --no-composer writes a project without Docker files, and the rest is Herd as with any Laravel app: herd link, herd secure, composer install, php artisan migrate, php artisan horizon and php artisan schedule:work in their own terminals. grim up, artisan, composer, test, update and require need the Docker stack; build, deploy and remote:* do not.