bref/laravel-bridge
Run Laravel on AWS Lambda with Bref. This bridge adapts Laravel for serverless deployment, handling queues, storage, and more, so you can scale without managing servers. Originally by CacheWerk, now maintained by Bref.
bref/laravel-bridge is the glue that lets a normal Laravel app run on AWS Lambda via Bref. It doesn't replace Bref — it adapts Laravel's assumptions (writable storage, file logging, long-running queue workers, php artisan) to Lambda's ephemeral, read-only-by-default execution model.
Minimal setup:
composer require bref/bref:^3.0.13 bref/bref bref/laravel-bridge
As of bridge 3.1.4, Bref 3.0.13 or newer is a hard requirement — earlier Bref releases missed the /tmp cleanup of uploaded files on PHP 8.4+, which is exactly the leak 3.1.4 fixes. If you're on PHP 8.4, bump Bref in the same commit.
The bridge registers Bref\LaravelBridge\BrefServiceProvider via package discovery, so there's usually nothing to add to config/app.php or bootstrap/providers.php. From there:
serverless.yml with an HTTP function pointing at public/index.php and the php-8x-fpm runtime.LOG_CHANNEL=stderr, VIEW_COMPILED_PATH=/tmp/views, APP_STORAGE=/tmp/storage, and a non-file cache/session driver.serverless deploy (or vendor/bin/bref deploy).Where to look first: serverless.yml (runtimes, handlers, timeouts), config/logging.php (stderr channel), bootstrap/app.php / config/filesystems.php (storage paths), and the bridge's own README for the current Laravel-version-specific wiring. If you have a config/bref.php, open it once: the bref.request_context option there is now deprecated and does nothing useful on Lambda.
First real use case: ship an existing Laravel HTTP app unchanged to Lambda. Once that works, the next step is almost always a second artisan function for scheduled commands — that's where the bridge starts earning its keep.
One codebase, multiple functions. The idiomatic layout is a single deploy containing a web function (handler: public/index.php, php-8x-fpm) plus one or more console functions (handler: artisan, php-8x) for schedules, migrations, and queue workers. Share env vars via provider.environment and per-function overrides only when timeouts/memory genuinely differ.
Scheduled tasks. Wire an EventBridge schedule event to an artisan function. The bridge makes php artisan schedule:run (or a specific command) behave correctly inside Lambda, so app/Console/Kernel.php / routes/console.php stays the single source of truth.
Queue workers. Point an SQS-triggered function at the artisan handler and run queue:work. Because Lambda caps at 15 minutes, always bound the worker (--max-time slightly under the function timeout, or --stop-when-empty) and match the SQS visibility timeout to the function timeout. Keep job payloads small; the message is the unit of retry.
Failed jobs need somewhere to land. Configure failed_jobs deliberately: the default database-uuids driver assumes a reachable database. Since 3.1.4 you no longer lose the original exception when persistence fails — both the job's exception and the storage error are reported — but the job is still gone, so use DynamoDB/SQS-backed or table-backed failure storage that you've actually provisioned. Watch the logs for the new "could not store failed job" report; it means your failure path is misconfigured, not that jobs are succeeding.
Uploads must be handled inside the request. Uploaded files live in the instance's /tmp; as of 3.1.4 they are deleted when the next request starts (Octane and Bref\LaravelBridge\Http\HttpHandler). So: validate → move to S3 → persist the key, all in the same request. Never enqueue a job that reads $request->file(...)->getRealPath() or a storage/app/... temp path and assume the bytes will still be there.
Filesystem split. Treat /tmp as scratch space only (compiled views, framework cache, in-flight uploads) and put anything durable — uploads, generated reports, exported PDFs — on S3 (FILESYSTEM_DISK=s3). Sessions and cache belong on Redis, DynamoDB, or the database, never file. The 3.1.4 upload cleanup is a safety net for request-scoped temp files, not a licence to put durable data in /tmp.
Logging. Route everything to stderr and let CloudWatch capture it. Use a JSON formatter so CloudWatch Logs Insights queries stay cheap and grep-able. The bref/monolog-bridge package is the piece that makes Monolog play nicely with Lambda's stdout/stderr capture — 3.1.3 relaxes its constraint to allow v2.0, which unblocks fresh installs that also pull Monolog 3 / bridge 2. Request IDs are now emitted by Bref's own log formatter (each line is prefixed), so drop bref.request_context from your config and rely on that.
Build-time warming. Run php artisan config:cache, route:cache, and view:cache during CI, not at cold start, and deploy bootstrap/cache alongside vendor/. This is the single biggest cold-start lever.
Local dev. Keep php artisan serve for day-to-day work; the bridge is a deployment concern, not a development one. Reserve bref local / serverless invoke local for verifying handler wiring and env-var assumptions before deploying. Note that artisan serve won't reproduce the 3.1.4 upload-cleanup semantics — test that path through the Lambda/Octane runtime.
/tmp is per-instance and ephemeral. Never assume a file written by one invocation exists in the next. Uploaded request files are now swept at the start of the next request, so on a warm instance they linger for one cycle and then vanish — plenty of time to pass a test locally, not enough for an async job to pick them up./tmp for the request. If you deliberately relocated an upload to /tmp yourself (e.g. Storage::disk('local') with APP_STORAGE=/tmp/storage) and expect it to survive, that's the same bug the release fixes; move it to S3.composer update of the bridge alone may not be enough — check composer show bref/bref.config:cache bakes env values at build time. If you cache config in CI, runtime env changes (secrets rotated via SSM, per-stage URLs) won't apply. Either inject them before the cache step or skip config caching for values that must vary per deploy.bref.request_context is deprecated and slated for removal in the next major. It injected X-Request-ID into the shared log context, but API Gateway never sends that header, so it was a no-op for PHP-FPM. Remove it from config/bref.php (or your env) rather than relying on it — Bref's log formatter already prefixes request IDs on Lambda.file sessions and file cache pass a single invocation and then lose state across instances. Default to Redis/DynamoDB/database early.queue:work loops will be killed mid-job. Bound them, and make jobs idempotent — SQS redelivery is normal, not exceptional.serverless.yml. Use SSM Parameter Store or Secrets Manager and resolve at runtime; APP_KEY especially.daily file channel will quietly vanish.bref/monolog-bridge:^2.0 alongside the bridge produced a Composer conflict; 3.1.3 widens the constraint to allow it. If you're on older 3.1.x and hitting that conflict, this is the reason to bump.vendor/ lean (composer install --no-dev --optimize-autoloader), pre-cache config/views/routes, and measure before optimizing further.BrefServiceProvider is the seam for customizing storage paths, log channels, and queue behavior. Prefer overriding via config/env over forking the provider; you'll want to stay on the upgrade path as Bref tracks new PHP runtimes.How can I help you explore Laravel packages today?