Architecture
How a request travels from the browser through Anvil DNS and the web server to PHP-FPM or an app dev server.
browser ──▶ Anvil DNS (*.test → 127.0.0.1)
──▶ ports 80/443, forwarded by pf to 8080/8443 (optional)
──▶ Caddy (HTTP, HTTPS per site, local certificates, 127.0.0.1 only)
├─▶ PHP-FPM: a shared pool per PHP version, own pools for customised sites
├─▶ static files
└─▶ app dev servers on private ports (Node.js, Rust, Go, .NET, Python)
services (MySQL, PostgreSQL, Redis, Mailpit) ◀── your applications, 127.0.0.1 onlyPieces
- DNS. A small DNS server built into Anvil listens on 127.0.0.1 (port 5354
by default). It answers
127.0.0.1for every name under the domain suffix and refuses everything else. macOS only asks it about*.testbecause of/etc/resolver/test; all other lookups are untouched. - Caddy is the web server. Anvil downloads it into
~/.anvil/binand generates~/.anvil/run/Caddyfilefrom your sites: one block per site that serves files and forwards PHP to PHP-FPM, or proxies to an app's dev server. Sites are HTTP unless HTTPS is on for them; for those, Caddy issues certificates from its own local certificate authority, whichanvil trustadds to your keychain, and redirectshttp://tohttps://. Dotfiles are never served. - PHP-FPM. One shared pool per installed PHP version serves every site on that version. A site with its own php.ini values gets its own pool so its settings never leak into other sites.
- App dev servers. Applications that bring their own server (Node.js, Rust,
Go, .NET, Python) are started by Anvil with the language toolchain from
~/.anvil/runtimes, on a private port passed in$PORT, in their own process group so their child processes stop with them. - Services run separately from the web server, so a site change never restarts a database. Each has its own port, data folder, log and pid file.
Supervision
anvil start (or the app) runs everything as child processes and records their
pids in ~/.anvil/run. If Anvil is killed, it cleans up those leftover processes
on the next start. Stopping sends SIGTERM with a grace period before SIGKILL.
Everything lives in ~/.anvil (see Configuration).