Search by

brewless / laravel

Jaspur

What a Laravel application needs to run on Brewless: the release, schedule and queue routes its own containers answer on, and trust in the edge in front of it.

v0.2.0 2026-10-07 18:44 UTC

This package is auto-updated.

Last update: 2026-10-07 18:45:03 UTC


README

What a Laravel application needs to run on Brewless. It runs in your containers, in your own cloud account. It does not call Brewless, sends nothing anywhere and keeps working when you stop using Brewless.

composer require brewless/laravel

There is nothing to register. Laravel 12 and 13, PHP 8.3 and up.

What it adds

Release operations over HTTP

Nothing stays running on a serverless container, and a one-off job cannot reach a private database. So the containers of an environment answer on a few routes of their own:

Route What it does
POST /_ops/migrate Runs the release commands (migrate --force) before a release goes live
POST / with {"task": "tick"} One scheduler tick, then the queue; the body a cron trigger sends
POST /_ops/schedule, POST /_ops/queue The same two, separately
GET /_ops/status Last tick, jobs waiting, jobs failed (which, never why)
POST /_ops/queue/retry Puts every failed job back on the queue
POST /_ops/command Runs one console command, without questions

Every route needs the secret from BREWLESS_OPS_SECRET, in the header X-Brewless-Ops or as secret in the JSON body. A container without that variable has none of these routes: they answer 404, the same as a path that does not exist. Give the secret only to the containers that should do this work; the container that serves visitors does not need it.

Commands that never return (queue:work, tinker, serve, …) and commands that throw data away (migrate:fresh, db:wipe, …) are refused on /_ops/command. An address that gives a wrong secret ten times in a minute gets the same 404 for a minute, whatever it sends. A tick that finds no job due does not start a worker, so an idle application costs a single query.

The visitor's hostname behind the edge

Scaleway routes a request on its Host header and only knows the container's own domain, so the edge sends that Host and carries the real hostname in Cdn-Host. The package puts it back, but only when the request also carries the secret from BREWLESS_EDGE_SECRET, and only for the domains in BREWLESS_DOMAINS (default: the host of APP_URL) and their subdomains. Calling the container directly with a made-up hostname changes nothing.

The visitor's address is treated the same way. With an edge secret set, only the entry the edge appended to X-Forwarded-For is kept, and a request without the secret loses the header, so rate limits count the real visitor and nobody chooses their own address. BREWLESS_EDGE_HOPS says how many proxies append to the header (default 1). Without an edge secret the header is left as it arrived.

Settings

Variable Meaning
BREWLESS_OPS_SECRET Turns the ops routes on, on this container
BREWLESS_EDGE_SECRET The secret the edge adds to every request
BREWLESS_DOMAINS Comma-separated domains that are yours
BREWLESS_EDGE_HOPS Proxies between the visitor and the container (1)
BREWLESS_HEARTBEAT_STORE Cache store for the sign of life (file)
SQS_ACCESS_KEY_ID, SQS_SECRET_ACCESS_KEY, SQS_ENDPOINT The key and address of a queue at Scaleway Queues; handed to the sqs connection, which otherwise shares AWS_* with the s3 disk
RELEASE The running image; set by Brewless on each release

Other release commands, queue time and tries, or more commands to refuse:

php artisan vendor:publish --tag=brewless-config

Your config/brewless.php only has to name what it changes; every setting it leaves out keeps the package's value.

Without Brewless

The routes are plain HTTP. A cron trigger of your provider that posts {"secret": "…", "task": "tick"} to the worker container every minute is all the scheduler and the queue need; curl -X POST -H "X-Brewless-Ops: …" https://<container>/_ops/migrate runs your migrations.

Development

composer install
vendor/bin/pest
vendor/bin/pint

Licence

MIT. See LICENSE.