Per-project butler.yml
Drop a butler.yml file at the root of any linked or parked project to override
Butler’s defaults for that project alone. Every field is optional — a site
without the file inherits your global config and Butler’s auto-detection. A
valet.yml file is accepted as a fallback name for projects coming from Valet.
Generate a starter file with butler init in the project directory.
Full example
Section titled “Full example”name: my-app # hostname label → my-app.test (see below)php: "8.3" # runtime versionwebserver: frankenphp-octane # caddy | nginx | frankenphp | frankenphp-octanedriver: laravel # overrides auto-detectionpublicPath: public # overrides the driver's default docroot
env: # extra environment exposed to PHP APP_DEBUG: "true"
database: # optional DB hookup for `butler link` type: mysql name: my_app_local createOnLink: true
scripts: # lifecycle hooks (run with cwd = site dir) postLink: "composer install && php artisan migrate" preUnlink: "php artisan app:cleanup"
server: # web-server tuning clientMaxBodySize: "100M" ssi: true # nginx Server Side Includes (nginx only) phpSettings: memory_limit: "1G"
ssl: # bring your own cert instead of Butler's CA cert: /abs/path/to/site.pem key: /abs/path/to/site-key.pem
proxy: # reverse-proxy mode (with driver: proxy) target: "127.0.0.1:3000" # your own dev server (Vite, Next, etc.) websocket: true # tunnel WebSocket upgrades; default true headers: X-Source: butler
octane: # only when webserver is frankenphp-octane workers: 8 max_requests: 500 watch: - app - routes - config - .envField reference
Section titled “Field reference”Identity
Section titled “Identity”name— the hostname label the site is served at (<name>.<tld>). Useful when the project folder isn’t the name you want. Precedence at link time: an explicitbutler link <name>wins, then thisname, then the first.envsite URL host that’s under your TLD (APP_URL, thenPRIMARY_SITE_URL, thenDEFAULT_SITE_URL), then the directory name. It’s read only when linking — changing it means re-runningbutler link.
Runtime
Section titled “Runtime”php— the PHP version for this site, e.g."8.3". Equivalent tobutler isolatebut committed with the repo. See PHP versions.webserver—caddy,nginx,frankenphp, orfrankenphp-octane.driver— force a framework handler instead of auto-detection (laravel,wordpress,symfony,drupal,statamic,craft,proxy,static,custom).publicPath— the web root, relative to the project, when it isn’t the driver default.
Environment & database
Section titled “Environment & database”-
env— extra environment variables exposed to PHP. -
database— withcreateOnLink: true,butler linkcreates the database named indatabase.nameon a running instance ofdatabase.type(mysql,mariadb, orpostgres). It connects as the default local superuser (rootfor MySQL/MariaDB,postgresfor PostgreSQL, no password) and is idempotent — an existing database is left alone. Best-effort: if no matching service is running (or it’s unreachable), the link still succeeds and reports why the database wasn’t created. See Databases & services and Connecting to databases.database:type: mysqlname: my_app_localcreateOnLink: true
Lifecycle scripts
Section titled “Lifecycle scripts”Both run via sh -c with the working directory set to the project root, and with
Butler’s active PHP and Composer prepended to PATH — so php,
composer and artisan resolve to Butler’s managed binaries without any shell
setup. They’re best-effort: a failing hook is reported in the command output
but never rolls back the link/unlink. Each is bounded by a 15-minute timeout
(enough for a cold composer install).
scripts.postLink— run after the site is linked — e.g. install dependencies and run migrations:"composer install && php artisan migrate".scripts.preUnlink— run before the site is unlinked, while the project is still in place — e.g."php artisan app:cleanup".
Web server
Section titled “Web server”server.clientMaxBodySize— max request body / upload size, e.g."100M"or"1G". Applied on both nginx (client_max_body_size) and Caddy/FrankenPHP (request_body max_size). Use whole-number, nginx-style units (100M,1G); a Caddy-style"100MB"is normalized to"100M"so it works on both, but nginx has no fractional form. This is the web server’s limit — for large uploads also raise PHP’spost_max_size/upload_max_filesizeviaserver.phpSettings.server.ssi— settrueto enable nginx Server Side Includes (ssi on;) for the site. nginx only — Caddy and FrankenPHP don’t implement classic SSI, so pair it withwebserver: nginx.server.phpSettings— per-sitephp.inioverrides such asmemory_limitorupload_max_filesize. Passed to PHP-FPM as a per-requestPHP_VALUE(the FPM pool is shared across all sites on a version), so it only affects settings that are changeable at runtime (PHP_INI_ALL/PHP_INI_PERDIR) — notPHP_INI_SYSTEMones. Works on both nginx and Caddy/FrankenPHP.
ssl.cert/ssl.key— absolute paths to your own certificate and key, used instead of Butler’s CA. See HTTPS & trusted certs.
Reverse proxy
Section titled “Reverse proxy”proxy.target— withdriver: proxy, forward to a dev server you run yourself (Vite, Next, etc.).proxy.websocket— tunnel WebSocket upgrades (defaulttrue).proxy.headers— extra headers to add to proxied requests.
Octane
Section titled “Octane”Only applies when webserver is frankenphp-octane:
octane.workers— number of worker processes.octane.max_requests— requests a worker handles before recycling.octane.watch— paths that trigger a reload when they change.