# Bref documentation > Bref is an open-source framework to run PHP applications on AWS Lambda (serverless). This file contains the whole documentation of https://bref.sh. Each page starts with its canonical URL. --- Source: https://bref.sh/docs # What is Bref and serverless? Serverless means using cloud services that manage the servers for us. ## Why serverless? When running PHP on a server, we must: - set up, configure, and maintain that server, - pay a fixed price for the server, - scale the server(s) if we get more traffic. When running PHP serverless: - We do not need to set up servers, the cloud provider takes care of that. - We pay only for what we use (per request). - Our application scales automatically. **Serverless provides more scalable, affordable, and reliable architectures with less effort.** Serverless includes services like storage as a service, database as a service, message queue as a service, etc. One service in particular is interesting for us developers: *Function as a Service* (FaaS). FaaS is a way to run code where the hosting provider takes care of setting up everything, keeping the application available 24/7, scaling it up and down, and we are only charged *while the code is actually executing*. ## Why Bref? Bref aims to make running PHP applications simple. To reach that goal, Bref takes advantage of serverless technologies. However, while serverless is promising, there are many choices to make, tools to build, and best practices to figure out. Bref provides extensive documentation and an entire toolkit to make serverless approachable and easy to use. ### What is Bref Bref (which means "brief" in French) comes as an open source Composer package and helps you deploy PHP applications to [AWS](https://aws.amazon.com) and run them on [AWS Lambda](https://aws.amazon.com/lambda/). It also makes it easy to use other AWS services (like S3, RDS, SQS…) for file storage, databases, etc. Bref provides: - documentation - PHP runtimes for AWS Lambda - deployment tooling - integrations for Laravel and Symfony The choice of AWS is deliberate: at the moment, AWS is the leading hosting provider, it is ahead in the serverless space in terms of features, performance, and reliability. AWS combines the advantages of being an extremely safe choice for hosting while providing the most advanced serverless solution. Bref configures and deploys applications to AWS using [the `serverless` CLI](https://github.com/oss-serverless/osls). Being the most popular tool, `serverless` comes with a huge community, a lot of examples online, and a simple configuration format. If you want to learn [how AWS Lambda and Bref work, read more here](https://bref.sh/docs/how-it-works). ## Use cases Bref and AWS Lambda can be used to run many kinds of PHP applications, for example: - APIs - websites - workers - batch processes/scripts - event-driven microservices Bref aims to support any PHP framework. It comes with deep integrations with Laravel and Symfony. If you are interested in real-world examples as well as cost analyses, head over to the [**Case Studies** page](https://bref.sh/docs/case-studies). ## Adapting to serverless > Do PHP applications need to be adapted to run serverless? In general no, but it depends on where you're coming from. Running PHP applications serverless comes with roughly the same constraints as running an application in auto-scaled containers. - The application code is mounted as read-only on disk. - Since the application scales horizontally to run in multiple "instances" (like containers), all these instances have independent and ephemeral filesystems. - Logs must not be written to disk, instead they must be sent to a centralized system (on AWS Lambda, logs written to `stderr` are automatically centralized in AWS CloudWatch, so this is usually a one-line config change). - Sessions must not be written to disk, instead they must be stored in a centralized system (e.g. the database or Redis). - Database and cache services (e.g. MySQL and Redis) must not run on the same server/container as PHP; instead, they must run in separate servers or containers (e.g. run databases in AWS RDS, Redis in AWS ElastiCache…). - Uploaded files and generated files (e.g. CSV exports, PDF files…) must be stored in a centralized system (e.g. AWS S3). - You can use the filesystem as a cache, but each instance of the application will then have a separate cache since the filesystem is not shared. If these sound familiar to you, good news: your application is ready for serverless. If your application is currently not set up like this, good news: preparing for serverless means you are actually preparing for a horizontally scalable application. These points are not "serverless-specific". ### Serverless-specific constraints There are still a few serverless-specific constraints to be aware of: - About 0.2% of all requests in production are ["cold starts"](https://bref.sh/docs/environment/performances#cold-starts), i.e. slower requests (AWS Lambda scaling up) that can add 200 ms to 500 ms (or even more on very large applications) to the HTTP response time. If your application cannot tolerate that occasional latency at the p99 metric, then serverless might not be the best fit. - AWS Lambda has a limit of ~4 MB for HTTP requests/uploaded files. If your application needs to support uploading larger files, a better option is to update your JavaScript frontend to upload files directly to AWS S3 via S3 pre-signed URLs (Laravel has helpers to generate these URLs for example). - We do not run long-running processes on AWS Lambda, like queue workers or websocket servers. For queue workers, this is actually great: SQS and Lambda integrate natively so that we don't have to run workers (or even think about them). Bref does the rest of the job to integrate natively with Laravel Queues or Symfony Messenger. More on this in the rest of the Bref documentation. For websocket servers, this means we cannot run servers like Ratchet or Laravel Reverb on Lambda. It is possible to run this on the side in an AWS EC2 server, or in a container in ECS, but this is extra setup. An alternative is to use the native API Gateway WebSocket feature, see the [WebSockets guide](https://bref.sh/docs/use-cases/websockets). - AWS Lambda has a maximum execution time limit of 15 minutes *per invocation*. Note that this limit applies "per invocation", i.e. per HTTP request, per SQS job, per CLI command, per cron task, etc. That does mean you cannot have one job or a PHP script running for more than 15 minutes. If you do, you can either try to split these tasks into smaller jobs, or run longer tasks in EC2 or ECS separately. ## Maturity matrix The matrix below provides an overview of the "maturity level" for common PHP applications. This maturity level is a vague metric, however it can be useful to anticipate the effort and the limitations to expect for each scenario. While a green note doesn't mean that Bref and Lambda are silver bullets for the use case (there are no silver bullets), a red note doesn't mean this is impossible or advised against. This matrix will be updated as Bref and AWS services evolve over time.
Simplicity Performance Reliability
Jobs, Cron
APIs
Websites
Legacy applications
Event-driven microservices
WebSockets
Real-time applications
Is this documented and simple to achieve? Is performance acceptable? Is this scenario production-ready?
Legend: Good use case Some drawbacks Strong limitations
- **Jobs, Cron** Jobs, cron tasks, and batch processes are very good candidates for FaaS. The scaling model of AWS Lambda can lead to very high throughput in queue processing, and the pay-per-use billing model can sometimes result in drastic cost reductions. Using Bref, it is possible to implement cron jobs and queue workers using PHP. Bref also provides integration with popular queue libraries, like Laravel Queues and Symfony Messenger. One limitation to keep in mind is that each AWS Lambda invocation has a maximum execution time of 15 minutes. - **API** APIs run on AWS Lambda without problems. Performance is now similar to what you could expect on a traditional VPS. The main difference to account for is that about 0.2% of HTTP requests are cold starts. If your use case requires that _all_ requests are handled in under 10 ms, serverless might not be a good fit. - **Website** Websites run well on AWS Lambda. Assets can be stored in S3 and served via CloudFront. This is documented in the ["Websites" guide](https://bref.sh/docs/use-cases/websites). Performance is as good as any server. - **Legacy application** Migrating a legacy PHP application to Bref and Lambda can be a challenge. You can expect to rewrite some parts of the code to make the application fit for Lambda (or running in containers in general). For example, file uploads and sessions often need to be adapted to work with the read-only file system. Cron tasks, scripts, or asynchronous jobs must be made compatible with Lambda and SQS. Not impossible, but definitely not the easiest place to start. As a first step, you can follow the guidelines of [The Twelve-Factor App](https://12factor.net). Note that if your application already runs redundantly on multiple servers, it is much closer to being ready for AWS Lambda and the migration could be simple. - **Event-driven microservices** Serverless is excellent for running event-driven microservice architectures. First of all, being able to standardize and orchestrate the deployment of multiple PHP microservices via a simple `serverless.yml` file and AWS CloudFormation simplifies deployments a lot. Every microservice can deploy in under a minute and easily reference other services or AWS resources. Each microservice then scales independently and in real time without thinking about provisioning or allocating resources, and while keeping costs aligned with usage (not paying for dozens or hundreds of idle containers). On top of that, `serverless.yml` and CloudFormation offer the ability to deeply integrate with other AWS services like SQS, EventBridge, SNS, DynamoDB, and more. Some teams also prefer to deploy everything with Terraform, which is less documented in Bref but doable for those experienced with Terraform. - **Websockets** It is possible to integrate PHP applications with API Gateway WebSocket, see the [WebSockets guide](https://bref.sh/docs/use-cases/websockets). However, there is currently no native integration with Laravel Echo. - **Real-time applications** Warm Lambda invocations are very fast (can be as low as 1 ms), but cold starts can take 200 ms or more. Cold starts are rare on most applications (about 0.2% of invocations) and can be further mitigated with [provisioned concurrency](https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html), but it's unlikely you can ensure they will _never_ happen. This makes Lambda a poor choice for real-time applications where latency **must** be below 100 ms for 100% of requests. ## Getting started Get started with Bref by reading the [installation documentation](https://bref.sh/docs/setup). Want to know if Bref is a good fit for you? Ask on [Slack](https://bref.sh/slack) in the `#help` channel. --- Source: https://bref.sh/docs/setup // Path relative to the copy in the `website/` folder # Setup You can deploy PHP applications with Bref using either: - [Bref Cloud](https://bref.sh/cloud) (simplest, most features, free trial) - or the [Serverless CLI](https://github.com/oss-serverless/osls) (more complex, fewer features built-in, free) Bref Cloud is the easiest way to deploy PHP applications. It simplifies setting up and managing AWS credentials, and it provides a dashboard to manage your applications, view logs, and more. Learn more about [Bref Cloud](https://bref.sh/cloud). This page will help you set up your environment for either option. Before getting started, you will need PHP (8.2 or greater) and [NPM](https://nodejs.org/) installed. ## Bref Cloud To use Bref Cloud, you will need a [Bref Cloud](https://bref.sh/cloud) account, an AWS account, and the `bref` CLI. Let's get started: ### Bref Cloud account First, visit [bref.cloud](https://bref.cloud) to create an account. ### AWS account Bref Cloud deploys your applications to your AWS account. To create an AWS account, **go to [aws.amazon.com](https://aws.amazon.com/) and click *Sign up***. Bref Cloud will guide you through the process of creating an AWS account and connecting it to Bref Cloud. If you want to learn more about how Bref Cloud connects securely to your AWS account, read the ["Security" documentation](https://bref.sh/docs/setup/cloud-security). AWS has a generous free tier that will usually allow you to deploy your first serverless applications for free. ### Bref CLI Next, let's install the Bref CLI: ```shell composer global require bref/cli ``` Finally, let's connect the CLI to your Bref Cloud account: ```shell bref login ``` If the `bref` command is not found, or if you want more details on how to install the CLI, read the detailed [installation instructions](https://bref.sh/docs/setup/cloud-getting-started). That's it, you're ready to use Bref with Bref Cloud! } title="Get started with Laravel" arrow="true" href="https://bref.sh/docs/laravel/getting-started" /> } title="Get started with Symfony" arrow="true" href="https://bref.sh/docs/symfony/getting-started" /> ## Serverless CLI If you don't want to use Bref Cloud, you can deploy PHP applications using the open-source [Serverless CLI](https://github.com/oss-serverless/osls). To use Bref with the Serverless CLI, you will need an AWS account, the `serverless` CLI, and AWS credentials. Let's get started: ### AWS account Bref deploys your applications to your AWS account. To create one, **go to [aws.amazon.com](https://aws.amazon.com/) and click *Sign up***. AWS has a generous free tier that will usually allow you to deploy your first serverless applications for free. ### Serverless CLI Bref relies on the [Serverless Framework](https://github.com/oss-serverless/osls) and AWS access keys to deploy applications. You will need to install the `serverless` CLI using NPM: ```bash npm install -g osls ``` > [!NOTE] > > The original [Serverless Framework](https://serverless.com/) is no longer open-source. An open-source alternative is [OSS Serverless](https://github.com/oss-serverless/osls), created and maintained by Bref maintainers. This is a drop-in replacement for the original CLI and is used throughout this documentation. ### AWS credentials Finally, we need AWS credentials so that the `serverless` CLI can deploy to AWS. > [!NOTE] > > If you have already set up AWS credentials on your machine (for example if you use the `aws` CLI), you can skip this step. - [Create AWS access keys](https://bref.sh/docs/setup/setup/aws-keys) - Set up those keys by running: ```bash serverless config credentials --provider aws --key "key" --secret "secret" ``` This will store the credentials in `~/.aws/credentials` (the [official file for AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)). This is the same as running the `aws configure` command with the `aws` CLI. Alternatively (for example in CI/CD), you can store credentials in environment variables: ```bash export AWS_ACCESS_KEY_ID=key export AWS_SECRET_ACCESS_KEY=secret ``` That's it, you're ready to use Bref with the Serverless CLI! } title="Get started with Laravel" arrow="true" href="https://bref.sh/docs/laravel/getting-started" /> } title="Get started with Symfony" arrow="true" href="https://bref.sh/docs/symfony/getting-started" /> > [!NOTE] > > Bref is compatible with PHP 8.2 or greater. > If you are using PHP 8.0 or 8.1, Bref v2 (previous major version) will be installed instead. --- Source: https://bref.sh/docs/laravel/getting-started # Serverless Laravel - Getting started This guide helps you run Laravel applications on AWS Lambda using Bref. These instructions are kept up to date to target the latest Laravel version. ## Setup First, **follow the [Setup guide](https://bref.sh/docs/setup)** to create an AWS account and install the necessary tools. Next, in an existing Laravel project, install Bref and the [Laravel-Bref package](https://github.com/brefphp/laravel-bridge). ```bash composer require bref/bref bref/laravel-bridge --update-with-dependencies ``` Then let's create a [`serverless.yml` configuration file](https://bref.sh/docs/environment/serverless-yml): ```bash php artisan vendor:publish --tag=serverless-config ``` ### How it works By default, the Laravel-Bref package will automatically configure Laravel to work on AWS Lambda. If you are curious, the package will automatically: - enable the `stderr` log driver, to send logs to CloudWatch ([read more about logs](https://bref.sh/docs/environment/logs)) - enable the [`cookie` session driver](https://laravel.com/docs/session#configuration) (if you prefer, you can configure sessions to be stored in database, DynamoDB or Redis) - move the storage directory to `/tmp` (because the default storage directory is read-only on Lambda) - adjust a few more settings ([have a look at the `BrefServiceProvider` for details](https://github.com/brefphp/laravel-bridge/blob/master/src/BrefServiceProvider.php)) ## Deployment We do not want to deploy "dev" caches that were generated on our machine (because paths will be different on AWS Lambda). Let's clear them before deploying: ```bash php artisan config:clear ``` When running in AWS Lambda, the Laravel application will automatically cache its configuration when booting. You don't need to run `php artisan config:cache` before deploying. Let's deploy now: ```bash serverless deploy ``` ```bash bref deploy ``` When finished, the `deploy` command will show the URL of the application. ### Deploying for production At the moment, we deployed our local codebase to Lambda. When deploying for production, we don't want to deploy: - development dependencies, - our local `.env` file, - or any other dev artifact. Follow [the deployment guide](https://bref.sh/docs/deploy#deploying-for-production) for more details about deploying in general. > [!WARNING] > > Most Laravel applications use `aws/aws-sdk-php` (pulled in by packages like SQS queues, S3 storage, etc.). This package is **very large** (100MB+) and can push your deployment over Lambda's 250MB size limit. Make sure to [remove unused AWS services](https://bref.sh/docs/deploy#reducing-package-size) to reduce the deployment size. Specifically for Laravel, Bref will automatically cache the configuration on "cold starts". This means that you don't need to run `php artisan config:cache` before deploying. > [!NOTE] > > You could improve the cold start time by pre-generating the config cache before deploying: > > ```bash > php artisan config:clear && php artisan config:cache > ``` > > However Laravel will **hardcode** absolute paths and environment variables in the cached configuration. To deploy a valid cached configuration, you would need to run the commands above in Docker with the application mounted in `/var/task` (the same path as on AWS Lambda), with production environment variables available. > > For most applications we do not recommend this approach. Instead, let Bref cache the config in AWS Lambda on cold starts. ## Troubleshooting In case your application is showing a blank page after being deployed, [have a look at the logs](https://bref.sh/docs/environment/logs). ## Logs Thanks to the Bref integration, Laravel will automatically log to CloudWatch via `stderr`. You don't have to do anything. You can learn more about logs in the [Logs guide](https://bref.sh/docs/environment/logs). ## Website assets Have a look at the [Website guide](https://bref.sh/docs/use-cases/websites) to learn how to deploy a website with assets. ## Laravel Artisan As you may have noticed, we define a function named "artisan" in `serverless.yml`. That function is using the [Console runtime](https://bref.sh/docs/runtimes/console), which lets us run Laravel Artisan on AWS Lambda. For example, to execute an `artisan` command on Lambda, run the command below: ```bash serverless bref:cli --args="{artisan command and options}" ``` For example: ```bash serverless bref:cli --args="route:list" ``` ```bash bref command "{artisan command and options}" ``` For example: ```bash bref command "route:list --help" ``` For more details follow [the "Console" guide](https://bref.sh/docs/runtimes/console). ### Laravel Tinker If you are using [Bref Cloud](https://bref.sh/cloud), you can start an interactive Tinker shell on AWS Lambda from your machine by running: ```bash bref tinker ``` The usual flags work, for example to run Tinker in the production environment: ```bash bref tinker --env=prod ``` ![](https://bref.sh/docs/laravel/tinker.png) > [!TIP] > > Make sure to update the `bref/laravel-bridge` package to version 3.0 or higher to use this feature. If you are not using Bref Cloud, you can check out this community package: [sls-tinker](https://github.com/datpmwork/sls-tinker). ## Inertia Laravel with Inertia runs without issue, like any other website. Follow the [Websites guide](https://bref.sh/docs/use-cases/websites) to learn how to deploy a Laravel application with assets. --- Source: https://bref.sh/docs/laravel/environments # Laravel environments Bref lets you deploy the same application multiple times in separate [environments](https://bref.sh/docs/deploy#environments), for example `dev`, `staging`, `prod`, or one environment per pull request. The Bref Cloud CLI calls them **environments** (`bref deploy --env=prod`), while the `serverless` CLI calls them **stages** (`serverless deploy --stage=prod`). This is just a vocabulary difference. Laravel also has an *application environment*, configured with the `APP_ENV` environment variable. These concepts are related, but they do not have to use the exact same name. ```bash bref deploy --env=prod ``` ```bash serverless deploy --stage=prod ``` In `serverless.yml`, the deployment environment is available as `${sls:stage}` for both CLIs: ```yml filename="serverless.yml" provider: environment: APP_ENV: ${param:appEnv} params: default: appEnv: ${sls:stage} prod: appEnv: production ``` In this example, most deployment environments use their name as `APP_ENV`, but the `prod` deployment environment is mapped to Laravel's `production` environment. > [!WARNING] > > Laravel treats `production` as the production environment. For example, `app()->isProduction()` returns `true` only when `APP_ENV=production`. > > Using `prod` as a deployment environment name is common because it is short and appears in AWS resource names such as Lambda functions and CloudFormation stacks. If you use `prod`, map it to `APP_ENV=production` for Laravel. ## Configure each environment [Parameters](https://bref.sh/docs/environment/serverless-yml#stage-parameters) are a convenient way to configure values that change between deployment environments. You can use them for Laravel environment variables: ```yml filename="serverless.yml" provider: environment: APP_ENV: ${param:appEnv} APP_DEBUG: ${param:debug} DB_CONNECTION: mysql DB_HOST: ${param:databaseHost} DB_DATABASE: ${param:databaseName} DB_USERNAME: root DB_PASSWORD: ${ssm:/my-app/${sls:stage}/db-password} params: default: appEnv: ${sls:stage} debug: 0 databaseHost: shared-dev.cluster-abc123.eu-west-1.rds.amazonaws.com databaseName: my_app_dev staging: databaseName: my_app_staging prod: appEnv: production databaseHost: prod.cluster-def456.eu-west-1.rds.amazonaws.com databaseName: my_app_prod ``` Parameters can also configure non-environment-variable values in `serverless.yml`, such as custom domains, certificates, VPC settings, or existing resource names: ```yml filename="serverless.yml" provider: vpc: ${param:vpc, ''} constructs: website: type: server-side-website domain: ${param:domain, ''} certificate: ${param:certificate, ''} params: default: domain: '' certificate: '' prod: domain: example.com certificate: arn:aws:acm:us-east-1:123456789012:certificate/... vpc: securityGroupIds: - sg-0123456789abcdef0 subnetIds: - subnet-0123456789abcdef0 - subnet-abcdef0123456789 ``` This keeps the configuration in one file while making the differences between environments explicit. ## Production isolation It is possible, and often recommended, to deploy production in a separate AWS account from development and staging environments. This gives production stronger isolation: - production data and resources are separated from test environments - permissions can be stricter for production deployments - experimental environments are less likely to affect production quotas or resources If you use Bref Cloud, you can connect multiple AWS accounts and deploy each environment to the right account without dealing with multiple credentials. ## Reusing resources Each deployment environment creates its own Lambda functions and CloudFormation stack. However, not every environment needs its own database, VPC, NAT gateway, or other expensive infrastructure. For staging, development, and preview environments, you can reuse shared resources by referencing them through parameters: ```yml filename="serverless.yml" params: default: databaseHost: shared-dev.cluster-abc123.eu-west-1.rds.amazonaws.com vpc: securityGroupIds: - sg-shareddev subnetIds: - subnet-shareddev-a - subnet-shareddev-b prod: databaseHost: prod.cluster-def456.eu-west-1.rds.amazonaws.com vpc: securityGroupIds: - sg-production subnetIds: - subnet-production-a - subnet-production-b ``` Preview environments can also share a development database cluster while using a different database name, schema, or prefix per environment. This approach reduces AWS costs and makes deployments faster because Serverless does not need to create a full VPC and database for every temporary environment. --- Source: https://bref.sh/docs/laravel/file-storage # File storage Laravel has a [filesystem abstraction](https://laravel.com/docs/filesystem) that lets us easily change where files are stored. When running on Lambda, you will need to use the **`s3` adapter** to store files on AWS S3. ## Quick setup with Lift The easiest way to set up S3 storage is using [Serverless Lift](https://github.com/getlift/lift): First install the Lift plugin: ```bash serverless plugin install -n serverless-lift ``` Then use [the `storage` construct](https://github.com/getlift/lift/blob/master/docs/storage.md) in `serverless.yml`: ```yaml filename="serverless.yml" provider: # ... environment: # environment variable for Laravel FILESYSTEM_DISK: s3 AWS_BUCKET: ${construct:storage.bucketName} constructs: storage: type: storage allowAcl: true ``` The [`allowAcl: true` configuration](https://github.com/getlift/lift/blob/master/docs/storage.md#acl-support) is needed because S3 buckets have ACLs disabled by default since April 2023. Many tools and libraries (including PHP's Flysystem, used by Laravel) send ACL headers on S3 operations, which will fail on buckets with ACLs disabled. The `allowAcl: true` setting lets the bucket accept these headers without errors. Note that files in the bucket are still completely private, there is no change in the security of the bucket. > [!TIP] > > To avoid silent failures, we also recommend setting `'throw' => true` on your S3 disk in `config/filesystems.php`: > > ```php filename="config/filesystems.php" > 's3' => [ > 'driver' => 's3', > // ... > 'throw' => true, > ], > ``` That's it! Lift automatically: - Creates the S3 bucket - Grants IAM permissions to your Lambda functions - Exposes the bucket name via `${construct:storage.bucketName}` The AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN) are set automatically in AWS Lambda, you don't have to define them. ## Uploading files ### Small files (< 4 MB) For files under 4 MB, you can upload directly through your Laravel application as usual: ```php $request->file('document')->store('documents'); ``` ### Large files AWS Lambda has a **6 MB request payload limit**, which leaves about 4 MB for the file itself because of base64 encoding. For larger files, you must upload directly to S3 from the browser using **presigned URLs**. Since the browser uploads directly to S3 (cross-origin), you need to configure **CORS** on the bucket and add a **lifecycle rule** to clean up temporary files. Here is a complete `storage` construct configuration: ```yaml filename="serverless.yml" constructs: storage: type: storage lifecycleRules: # Temporary upload files will be cleaned after 1 day - prefix: tmp/ expirationInDays: 1 allowAcl: true # CORS is required for uploading files from the browser via presigned URLs, put the URL of your website here # See https://github.com/getlift/lift/blob/master/docs/storage.md#cors cors: ${construct:website.url} ``` > [!TIP] > > If you are not using the `website` construct, replace `${construct:website.url}` with your application's URL, or use `'*'` during development. How it works: 1. Your frontend requests a presigned upload URL from your backend 1. Your backend generates a temporary presigned URL using Laravel's Storage 1. The frontend uploads the file directly to S3 1. The frontend sends the S3 key back to your backend to save in the database **Backend** - Generate presigned URL: `temporaryUploadUrl` returns an array with the URL and the headers that must be forwarded to S3 (they contain the request signature): ```php use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; public function presignedUploadUrl(): JsonResponse { $key = 'tmp/' . Str::uuid() . '.pdf'; // Generate a presigned PUT URL valid for 15 minutes $uploadUrl = Storage::temporaryUploadUrl($key, now()->addMinutes(15), [ // Optional: we restrict to PDF files here 'ContentType' => 'application/pdf', ]); // PSR-7 headers are string[] values and include Host, which browsers forbid $headers = collect($uploadUrl['headers']) ->except(['Host']) ->map(fn (array $values): string => implode(', ', $values)) ->all(); return response()->json([ 'url' => $uploadUrl['url'], 'headers' => $headers, 'key' => $key, ]); } ``` **Frontend** - Upload to S3: ```js // 1. Get presigned URL from your backend const { url, headers, key } = await fetch('/api/presigned-upload-url', { method: 'POST', headers: { 'X-CSRF-TOKEN': csrfToken }, }).then(r => r.json()); // 2. Upload directly to S3, forwarding the presigned headers await fetch(url, { method: 'PUT', body: file, headers: { 'Content-Type': file.type, ...headers, }, }); // 3. Send the S3 key to your backend (via a form field, API call, etc.) await fetch('/api/documents', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken, }, body: JSON.stringify({ file_key: key }), }); ``` **Backend** - Move file to final location: ```php public function store(Request $request) { $validated = $request->validate([ 'file_key' => 'required|string', ]); // Move from temporary location to final location $finalPath = "documents/{$document->id}.pdf"; Storage::move($validated['file_key'], $finalPath); // Save the final path in the database $document->update(['file_path' => $finalPath]); } ``` ## Downloading files For private files, generate temporary presigned URLs: ```php // Generate a presigned download URL valid for 15 minutes $url = Storage::temporaryUrl($document->file_path, now()->addMinutes(15)); return response()->json(['download_url' => $url]); ``` The URL can be used directly in the browser or in an `` tag. For local development, Laravel's filesystem abstraction lets you use the same code locally and in production. Laravel [supports temporary URLs for local files](https://laravel.com/docs/filesystem#temporary-urls) since version 9. ## Public files Some files are meant to be publicly accessible: uploaded photos, generated PDF files, avatars, etc. Laravel traditionally uses a [special disk called `public`](https://laravel.com/docs/filesystem#the-public-disk) for this, but with S3 we can use a simpler convention: **store public files under a `public/` prefix on the default (S3) disk**, and let Lift expose that prefix publicly. ### Expose the `public/` prefix with Lift Add [`publicPath: public`](https://github.com/getlift/lift/blob/master/docs/storage.md#public-files) to the `storage` construct. Every file stored under the `public/` prefix becomes publicly readable over HTTPS (via a bucket policy), while the rest of the bucket stays completely private: ```yaml filename="serverless.yml" {4} constructs: storage: type: storage publicPath: public allowAcl: true ``` ### Storing and reading public files Store public files under the `public/` prefix on the default disk. There is no need for a separate `public` disk, and no need to modify each file's visibility to `'public'` (this is useless with `publicPath`): ```php // Store a public file Storage::put('public/avatars/1.png', $fileContents); // Read its public URL $url = Storage::url('public/avatars/1.png'); ``` In production, set `AWS_URL` to the public bucket URL exposed by Lift so that `Storage::url()` returns the correct public URL: ```yaml filename="serverless.yml" provider: environment: FILESYSTEM_DISK: s3 AWS_BUCKET: ${construct:storage.bucketName} # Base URL used by `Storage::url()` to build public file URLs AWS_URL: ${construct:storage.publicUrl} ``` ### Making public files work locally Locally, the default `local` disk stores files in `storage/app/private`, so files written under the `public/` prefix end up in `storage/app/private/public`. To make things work, you can point the storage symlink to that directory in `config/filesystems.php`: ```diff 'links' => [ - public_path('storage') => storage_path('app/public'), + public_path('storage') => storage_path('app/private/public'), ], ``` If the `public/storage` symlink already exists, you need to remove it first so it can be recreated pointing to the new directory: ```bash rm public/storage php artisan storage:link ``` > [!WARNING] > > Do not run `php artisan storage:link` in AWS Lambda: it is useless there (files are served from S3), and it will fail because the filesystem is read-only. ### Migrating an existing application If your application already writes to the `public` disk and you don't want to change all those calls, keep the `public` disk but turn it into a [scoped disk](https://laravel.com/docs/filesystem#scoped-and-read-only-filesystems) of the default disk, scoped to the `public/` prefix. In `config/filesystems.php`: ```php filename="config/filesystems.php" 'public' => [ 'driver' => 'scoped', 'disk' => env('FILESYSTEM_DISK', 'local'), 'prefix' => 'public', ], ``` Scoped disks require the `league/flysystem-path-prefixing` package: ```bash composer require league/flysystem-path-prefixing "^3.0" ``` With this, calls like `Storage::disk('public')->put('avatars/1.png', ...)` keep working: they now write to `public/avatars/1.png` on the default disk, which Lift exposes publicly. --- Source: https://bref.sh/docs/laravel/queues # Laravel Queues To run Laravel Queues on AWS Lambda using [Amazon SQS](https://aws.amazon.com/sqs/), we don't want to run the `php artisan queue:work` command. Instead, we create a Lambda function that is invoked immediately when there are new jobs to process. To create the SQS queue (and the permissions for the Lambda functions to read/write to it), we can either do that manually, or use `serverless.yml`. To make things simpler, we will use the [Serverless Lift](https://github.com/getlift/lift) plugin to create and configure the SQS queue. First install the Lift plugin: ```bash serverless plugin install -n serverless-lift ``` Then use [the Queue construct](https://github.com/getlift/lift/blob/master/docs/queue.md) in `serverless.yml`: ```yml filename="serverless.yml" provider: # ... environment: # ... QUEUE_CONNECTION: sqs SQS_QUEUE: ${construct:jobs.queueUrl} functions: # ... constructs: jobs: type: queue worker: handler: Bref\LaravelBridge\Queue\QueueHandler runtime: php-84 timeout: 60 # seconds ``` We define Laravel environment variables in `provider.environment` (this could also be done in the deployed `.env` file): - `QUEUE_CONNECTION: sqs` enables the SQS queue connection - `SQS_QUEUE: ${construct:jobs.queueUrl}` passes the URL of the created SQS queue If you want to create the SQS queue manually, you will need to set these variables. AWS credentials (`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) are automatically set up with the appropriate permissions for Laravel to use the SQS queue. That's it! Anytime a job is pushed to Laravel Queues, it will be sent to SQS, and SQS will invoke our "worker" function so that it is processed. > [!TIP] > > In the example above, we set the full SQS queue URL in the `SQS_QUEUE` variable. > > If you only set the queue name (which is also valid), you need to set the `SQS_PREFIX` environment variable too. For example: `SQS_PREFIX: "https://sqs.${aws:region}.amazonaws.com/${aws:accountId}"`. ## How it works When integrated with AWS Lambda, SQS has a built-in retry mechanism and storage for failed messages. These features work slightly differently than Laravel Queues. The "Bref for Laravel" integration does **not** use these SQS features. Instead, "Bref for Laravel" makes all the features of Laravel Queues work out of the box, just like on any server. Read more in [the Laravel Queues documentation](https://laravel.com/docs/queues). > [!TIP] > > The "Bref-Laravel bridge" v1 used to do the opposite. We changed that behavior in Bref v2 in order to make the experience smoother for Laravel users. --- Source: https://bref.sh/docs/laravel/octane # Laravel Octane To run the HTTP application with [Laravel Octane](https://laravel.com/docs/octane) instead of PHP-FPM, change the following options in the `web` function: ```yml functions: web: handler: Bref\LaravelBridge\Http\OctaneHandler runtime: php-84 environment: BREF_LOOP_MAX: 250 # ... ``` Keep the following details in mind: - Laravel Octane does not need Swoole or RoadRunner on AWS Lambda, so it is not possible to use Swoole-specific features. - Octane keeps Laravel booted in a long-running process, [beware of memory leaks](https://laravel.com/docs/octane#managing-memory-leaks). - The process is kept alive between requests, but you still don't pay for time between requests. The execution model and cost model of AWS Lambda does not change (Lambda is frozen between requests). - `BREF_LOOP_MAX` specifies the number of HTTP requests handled before the PHP process is restarted (and the memory is cleared). > [!TIP] > > If you deploy using [container images](https://bref.sh/docs/deploy/docker), you must escape the `\` characters in your `Dockerfile`: > > ```dockerfile filename="Dockerfile" > CMD ["Bref\\LaravelBridge\\Http\\OctaneHandler"] > ``` ## Persistent database connections You can keep database connections persistent across requests to make your application even faster. To do so, set the `OCTANE_PERSIST_DATABASE_SESSIONS` environment variable: ```yml functions: web: handler: Bref\LaravelBridge\Http\OctaneHandler runtime: php-84 environment: BREF_LOOP_MAX: 250 OCTANE_PERSIST_DATABASE_SESSIONS: 1 # ... ``` Note that if you are using PostgreSQL (9.6 or newer), you need to set [`idle_in_transaction_session_timeout`](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT) either in your RDS database's parameter group, or on a specific database itself. ```sql ALTER DATABASE my_database SET idle_in_transaction_session_timeout = '10000'; -- 10 seconds in ms ``` --- Source: https://bref.sh/docs/laravel/passport # Laravel Passport Laravel Passport has a `passport:install` command. However, this command cannot be run in Lambda because it needs to write files to the `storage/` directory. Instead, here is what you need to do: ### Generate keys locally Run the following command on your machine to generate key files: ```bash php artisan passport:keys ``` This will generate the `storage/oauth-private.key` and `storage/oauth-public.key` files, which need to be deployed. Depending on how you deploy your application (from your machine, or from CI), you may want to whitelist them in `serverless.yml`: ```yml filename="serverless.yml" package: patterns: - ... # Exclude the 'storage' directory - '!storage/**' # Except the public and private keys required by Laravel Passport - 'storage/oauth-private.key' - 'storage/oauth-public.key' ``` ### Deploy You can now redeploy the application: ```bash serverless deploy ``` ```bash bref deploy ``` Note that during deployment the keys will be stored at the `./storage` path, not at `/var/task/storage`. The workaround is to adjust the Passport path with `Passport::loadKeysFrom('storage')`. ### Create tokens Finally, you can create the tokens (which is the second part of the `passport:install` command): ```bash serverless bref:cli --args="passport:client --personal --name 'Laravel Personal Access Client'" serverless bref:cli --args="passport:client --password --name 'Laravel Password Grant Client'" ``` ```bash bref command "passport:client --personal --name 'Laravel Personal Access Client'" bref command "passport:client --password --name 'Laravel Password Grant Client'" ``` All these steps were replacements of running the `passport:install` command [from the Passport documentation](https://laravel.com/docs/passport#installation). --- Source: https://bref.sh/docs/laravel/caching # Caching By default, the Bref bridge will move Laravel's storage and cache directories to `/tmp`. This is because all the filesystem except `/tmp` is read-only. However, the `/tmp` directory isn't shared across Lambda instances. If your Lambda function scales up or is redeployed, the cache will be empty in new instances. If you want the cache to be shared across all Lambda instances, for example if your application caches a lot of data or if you use it for locking mechanisms (like API rate limiting), you can instead use the database, Redis, or DynamoDB. If you are using a database already, using it as the cache driver is the simplest option. DynamoDB is a good alternative: fairly easy to set up, and "pay per use". Redis is a bit more complex as it requires a VPC and managing instances, but offers slightly faster response times. ## DynamoDB Cache To use DynamoDB as a cache store, set the following lines in `config/cache.php`: ```php filename="config/cache.php" {8-11} 'dynamodb' => [ 'driver' => 'dynamodb', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 'endpoint' => env('DYNAMODB_ENDPOINT'), 'attributes' => [ 'key' => 'id', 'expiration' => 'ttl', ] ], ``` Then follow [this section of the documentation](https://bref.sh/docs/environment/storage#deploying-dynamodb-tables) to deploy your DynamoDB table using the Serverless Framework. --- Source: https://bref.sh/docs/laravel/maintenance-mode # Maintenance mode Similar to the `php artisan down` command, you may put your app into maintenance mode. All that's required is setting the `MAINTENANCE_MODE` environment variable: ```yml filename="serverless.yml" provider: environment: MAINTENANCE_MODE: ${param:maintenance, null} ``` You can then deploy: ```bash # Full deployment (goes through CloudFormation): serverless deploy --param="maintenance=1" # Or quick update of the functions config only: serverless deploy function --function=web --update-config --param="maintenance=1" serverless deploy function --function=artisan --update-config --param="maintenance=1" serverless deploy function --function= --update-config --param="maintenance=1" ``` To take your app out of maintenance mode, redeploy without the `--param="maintenance=1"` option. --- Source: https://bref.sh/docs/symfony/getting-started # Serverless Symfony - Getting started This guide helps you run Symfony applications on AWS Lambda using Bref. These instructions are kept up to date to target the latest Symfony version. ## Setup First, **follow the [Setup guide](https://bref.sh/docs/setup)** to create an AWS account and install the necessary tools. Next, in an existing Symfony project, install Bref and the [Symfony Bridge package](https://github.com/brefphp/symfony-bridge). ```bash composer require bref/bref bref/symfony-bridge --update-with-dependencies ``` Next, create a `serverless.yml` configuration file at the root of your project: ```yml filename="serverless.yml" service: app # your application name (lowercase without spaces) bref: # Uncomment and set your team ID if you are using Bref Cloud #team: bref-team-id provider: name: aws region: us-east-1 # AWS region to deploy to environment: # Environment variables APP_ENV: prod functions: # This function runs the Symfony website/API web: handler: public/index.php runtime: php-84-fpm timeout: 28 # in seconds (API Gateway has a max timeout of 29 seconds) events: - httpApi: '*' # This function lets us run console commands in Lambda console: handler: bin/console runtime: php-84-console timeout: 120 # in seconds package: patterns: # Excluded files and folders for deployment - '!assets/**' - '!node_modules/**' - '!public/build/**' - '!tests/**' - '!var/**' # If you want to include files and folders that are part of excluded folders, # add them at the end - 'var/cache/prod/**' - 'public/build/entrypoints.json' - 'public/build/manifest.json' plugins: - ./vendor/bref/bref ``` You will also want to add `.serverless` to your `.gitignore`. You still have a few modifications to do on the application to make it compatible with AWS Lambda. Since [the filesystem is readonly](https://bref.sh/docs/environment/storage) except for `/tmp` we need to customize where the cache and logs are stored in the `src/Kernel.php` file. This is automatically done by the bridge, you just need to use the `BrefKernel` class instead of the default `BaseKernel`: ```diff filename="src/Kernel.php" namespace App; + use Bref\SymfonyBridge\BrefKernel; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; -use Symfony\Component\HttpKernel\Kernel as BaseKernel; - class Kernel extends BaseKernel + class Kernel extends BrefKernel { use MicroKernelTrait; } ``` ## Deployment Let's deploy the application to AWS Lambda: ```bash serverless deploy ``` ```bash bref deploy ``` When finished, the `deploy` command will show the URL of the application. ### Deploying for production At the moment, we deployed our local codebase to Lambda. When deploying for production, we probably don't want to deploy: - development dependencies, - our local `.env` files, - or any other dev artifact. Follow [the deployment guide](https://bref.sh/docs/deploy#deploying-for-production) for more details. Separately, on cold starts, Symfony will boot with an empty cache directory. It will build the cache on the fly, which can take a few seconds depending on the complexity of the application. To optimize cold starts, you can deploy the application with a warm cache. In a simple application it means that the deployment script should include `cache:warmup` to look something like this: ```bash # Install dependencies composer install --classmap-authoritative --no-dev --no-scripts # Warmup the cache bin/console cache:clear --env=prod # Disable use of Dotenv component echo " .env.local.php serverless deploy ``` ```bash # Install dependencies composer install --classmap-authoritative --no-dev --no-scripts # Warmup the cache bin/console cache:clear --env=prod # Disable use of Dotenv component echo " .env.local.php bref deploy ``` #### Optimizing caches When running Symfony on Lambda you should avoid writing to the filesystem. If you pre-warm the cache before deploying you are mostly fine. But you should also make sure you never write to a filesystem cache like `cache.system` or use a pool like: ```yaml framework: cache: pools: my_pool: adapter: cache.adapter.filesystem ``` If you don't write to such cache pool you can optimize your setup by not copying the `var/cache/pools` directory. The change below will make sure to symlink the `pools` directory. ```diff filename="src/Kernel.php" class Kernel extends BrefKernel { // ... + protected function getWritableCacheDirectories(): array + { + return []; + } } ``` ## Troubleshooting In case your application is showing a blank page after being deployed, [have a look at the logs](https://bref.sh/docs/environment/logs). ## Logs Thanks to the Bref integration, Symfony will automatically log to CloudWatch via `stderr`. You don't have to do anything. You can learn more about logs in the [Logs guide](https://bref.sh/docs/environment/logs). ## Website assets Have a look at the [Website guide](https://bref.sh/docs/use-cases/websites) to learn how to deploy a website with assets. ## Symfony Console As you may have noticed, we define a function named "console" in `serverless.yml`. That function is using the [Console runtime](https://bref.sh/docs/runtimes/console), which lets us run the Symfony Console on AWS Lambda. For example, to execute a `bin/console` command on Lambda, run the command below: ```bash serverless bref:cli --args="{console command and options}" ``` For example: ```bash serverless bref:cli --args="doctrine:migrations:migrate" ``` ```bash bref command "{console command and options}" ``` For example: ```bash bref command "doctrine:migrations:migrate" ``` For more details follow [the "Console" guide](https://bref.sh/docs/runtimes/console). ## Trust API Gateway When hosting a website on Lambda, API Gateway acts as a proxy between the client and your Lambda function. By default, Symfony doesn't trust proxies for security reasons, but it's safe to do it when using API Gateway and Lambda. This is needed because otherwise, Symfony will not be able to generate URLs properly. Add the following lines to `config/packages/framework.yaml`: ```yml filename="config/packages/framework.yaml" {2-5} framework: # trust the remote address because API Gateway has no fixed IP or CIDR range that we can target trusted_proxies: '127.0.0.1' # trust "X-Forwarded-*" headers coming from API Gateway trusted_headers: [ 'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-port' ] ``` Note that API Gateway doesn't set the `X-Forwarded-Host` header, so we don't trust it by default. You should only whitelist this header if you set it manually, for example in your CloudFront configuration (this is done automatically in [the CloudFront distribution deployed by Lift](https://bref.sh/docs/use-cases/websites)). You can get more details in the [Symfony documentation](https://symfony.com/doc/current/deployment/proxies.html). > [!TIP] > > Be careful with these settings if your app is also executed outside a Lambda environment (for example on a public server). ### Getting the user IP **When using CloudFront** on top of API Gateway, you will not be able to retrieve the client IP address, and you will instead get one of CloudFront's IP when calling `Request::getClientIp()`. If you really need this, you will need to whitelist [every CloudFront IP](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/LocationsOfEdgeServers.html) in `trusted_proxies`. ## The `kernel.terminate` event The [`kernel.terminate` event](https://symfony.com/doc/current/components/http_kernel.html#component-http-kernel-kernel-terminate) runs **synchronously** on Lambda. That means that if you use this event, its listeners will be executed **before** the Lambda function returns its response. That will add latency to your response. To run asynchronous tasks, use the [Symfony Messenger](https://bref.sh/docs/symfony/messenger) instead. --- Source: https://bref.sh/docs/symfony/messenger # Symfony Messenger Symfony Messenger messages can be dispatched to **SQS, SNS, or EventBridge**, while workers handle those messages on AWS Lambda. ## Installation This guide assumes that: - Symfony and [Symfony Messenger are installed](https://symfony.com/doc/current/messenger.html#installation) - Bref is [installed and set up with Symfony](https://bref.sh/docs/symfony/getting-started) First, install the [Bref-Symfony messenger integration](https://github.com/brefphp/symfony-messenger): ```bash composer require bref/symfony-messenger ``` Next, register the bundle in `config/bundles.php`: ```php filename="config/bundles.php" {3} return [ // ... Bref\Symfony\Messenger\BrefMessengerBundle::class => ['all' => true], ]; ``` SQS, SNS, and EventBridge can now be used with Symfony Messenger. ## Usage Symfony Messenger dispatches messages. To create a message, follow the [Symfony Messenger documentation](https://symfony.com/doc/current/messenger.html#creating-a-message-handler). To configure **where** messages are dispatched, all the examples in this documentation are based on [the example from the Symfony documentation](https://symfony.com/doc/current/messenger.html#transports-async-queued-messages): ```yml filename="config/packages/messenger.yaml" framework: messenger: transports: async: '%env(MESSENGER_TRANSPORT_DSN)%' routing: 'App\Message\MyMessage': async ``` ## SQS The [SQS](https://aws.amazon.com/sqs/) service is a queue that is similar to RabbitMQ. To use it, set its URL in the environment variable `MESSENGER_TRANSPORT_DSN`: ```yml filename="serverless.yml" {4} provider: name: aws environment: MESSENGER_TRANSPORT_DSN: https://sqs.us-east-1.amazonaws.com/123456789/my-queue ``` The implementation uses the SQS transport provided by [Symfony Amazon SQS Messenger](https://symfony.com/doc/current/messenger.html#amazon-sqs), so all its features are supported. If you already use that transport, the transition to AWS Lambda should not require any change for dispatching messages. However, instead of creating the SQS queue and the worker manually, you can use the [Serverless Lift](https://github.com/getlift/lift) plugin. First install the Lift plugin: ```bash serverless plugin install -n serverless-lift ``` Then use [the Queue construct](https://github.com/getlift/lift/blob/master/docs/queue.md) in `serverless.yml` to create a queue and a worker: ```yml filename="serverless.yml" provider: # ... environment: # ... MESSENGER_TRANSPORT_DSN: ${construct:jobs.queueUrl} functions: # ... constructs: jobs: type: queue worker: handler: bin/consumer.php runtime: php-84 timeout: 60 # in seconds ``` You will want to disable `auto_setup` to avoid useless extra SQS requests and permission issues. ```yml filename="config/packages/messenger.yaml" {6-7} framework: messenger: transports: async: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' options: auto_setup: false ``` With that configuration, anytime a message is pushed to Symfony Messenger, it will be sent to SQS, and SQS will automatically invoke our "worker" Lambda function so that it is processed. > [!TIP] > > With Lift, AWS credentials (`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) are automatically set up with the appropriate permissions for Messenger to use the SQS queue. We now need to create the handler script (`bin/consumer.php`): ```php filename="bin/consumer.php" bootEnv(dirname(__DIR__).'/.env'); $kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool)$_SERVER['APP_DEBUG']); $kernel->boot(); // Return the Bref consumer service return $kernel->getContainer()->get(SqsConsumer::class); ``` Finally, register and configure the `SqsConsumer` service: ```yml filename="config/services.yaml" services: Bref\Symfony\Messenger\Service\Sqs\SqsConsumer: public: true autowire: true arguments: $partialBatchFailure: true ``` ### Error handling AWS Lambda has error handling mechanisms (retrying and handling failed messages). Because of that, this package does not integrate Symfony Messenger's retry mechanism. Instead, it works with Lambda's retry mechanism. With the default Lift configuration, failed messages will be retried 3 times. You can configure this, [learn more](https://github.com/getlift/lift/blob/master/docs/queue.md#retries). When using SNS and EventBridge, messages will be retried by default 2 times. ### FIFO queue [FIFO queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html) guarantee exactly once delivery, and have a mandatory queue name suffix `.fifo`. With Lift, [set `fifo: true`](https://github.com/getlift/lift/blob/master/docs/queue.md#fifo-first-in-first-out) to enable it: ```yml filename="serverless.yml" {4} constructs: my-queue: # ... fifo: true ``` [Symfony Amazon SQS Messenger](https://symfony.com/doc/current/messenger.html#amazon-sqs) will automatically calculate/set the `MessageGroupId` and `MessageDeduplicationId` parameters required for FIFO queues, but you can set them explicitly: ```php use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsFifoStamp; /* @var MessageBus $messageBus */ $messageBus->dispatch(new MyAsyncMessage(), [ new AmazonSqsFifoStamp('my-group-message-id', 'my-deduplication-id'), ]); ``` Everything else is identical to the normal SQS queue. ## SNS AWS [SNS](https://aws.amazon.com/sns) is "notification" instead of "queues". Messages may not arrive in the same order as sent, and they might arrive all at once. To use it, create an SNS topic and set it as the DSN: ```dotenv MESSENGER_TRANSPORT_DSN=sns://arn:aws:sns:us-east-1:1234567890:foobar ``` That's it, messages will be dispatched to that topic. > [!TIP] > > When running Symfony on AWS Lambda, it is not necessary to configure credentials. The AWS client will read them [from environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime) automatically. To consume messages from SNS: 1. Create the function that will be invoked by SNS in `serverless.yml`: ```yml filename="serverless.yml" functions: worker: handler: bin/consumer.php timeout: 20 # in seconds runtime: php-84 events: # Read more at https://github.com/oss-serverless/osls/blob/4.x/docs/events/sns.md - sns: arn: arn:aws:sns:us-east-1:1234567890:my_sns_topic ``` 2. Create the handler script (for example `bin/consumer.php`): ```php filename="bin/consumer.php" bootEnv(dirname(__DIR__).'/.env'); $kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); $kernel->boot(); // Return the Bref consumer service return $kernel->getContainer()->get(SnsConsumer::class); ``` 3. Register and configure the `SnsConsumer` service: ```yml filename="config/services.yaml" services: Bref\Symfony\Messenger\Service\Sns\SnsConsumer: public: true autowire: true ``` Now, anytime a message is dispatched to SNS, the Lambda function will be called. The Bref consumer class will put back the message into Symfony Messenger to be processed. ### Error handling AWS Lambda has error handling mechanisms (retrying and handling failed messages). Because of that, this package does not integrate Symfony Messenger's retry mechanism. Instead, it works with Lambda's retry mechanism. By default, Lambda will retry failed messages 2 times. ## EventBridge AWS [EventBridge](https://aws.amazon.com/eventbridge/) is a message routing service. It is similar to SNS, but more powerful for communication between microservices. To use it, configure the DSN like so: ```dotenv # "myapp" is the EventBridge "source", i.e. a namespace for your application's messages # This source name will be reused in `serverless.yml` later. MESSENGER_TRANSPORT_DSN=eventbridge://myapp ``` Optionally you can set the [EventBusName](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEventsRequestEntry.html#eventbridge-Type-PutEventsRequestEntry-EventBusName) via an `event_bus_name` query parameter, either the name or the ARN: ```dotenv MESSENGER_TRANSPORT_DSN=eventbridge://myapp?event_bus_name=custom-bus MESSENGER_TRANSPORT_DSN=eventbridge://myapp?event_bus_name=arn:aws:events:us-east-1:123456780912:event-bus/custom-bus ``` That's it, messages will be dispatched to EventBridge. > [!TIP] > > When running Symfony on AWS Lambda, it is not necessary to configure credentials. The AWS client will read them [from environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime) automatically. To consume messages from EventBridge: 1. Create the function that will be invoked by EventBridge in `serverless.yml`: ```yml filename="serverless.yml" functions: worker: handler: bin/consumer.php timeout: 20 # in seconds runtime: php-84 events: # Read more at https://github.com/oss-serverless/osls/blob/4.x/docs/events/event-bridge.md - eventBridge: # If you changed the bus name in config/packages/messenger.yaml (e.g. eventbridge://myapp?event_bus_name=custom-bus), set it here too: # eventBus: custom-bus # This filters events we listen to: only events from the "myapp" source. # This should be the same source defined in config/packages/messenger.yaml pattern: source: - myapp ``` 2. Create the handler script (for example `bin/consumer.php`): ```php filename="bin/consumer.php" bootEnv(dirname(__DIR__).'/.env'); $kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); $kernel->boot(); // Return the Bref consumer service return $kernel->getContainer()->get(EventBridgeConsumer::class); ``` 3. Register and configure the `EventBridgeConsumer` service: ```yml filename="config/services.yaml" services: Bref\Symfony\Messenger\Service\EventBridge\EventBridgeConsumer: public: true autowire: true arguments: # Pass the transport name used in config/packages/messenger.yaml $transportName: 'async' # Optionally, if you have different buses in config/packages/messenger.yaml, set $bus like below: # $bus: '@event.bus' ``` Now, anytime a message is dispatched to EventBridge for that source, the Lambda function will be called. The Bref consumer class will put back the message into Symfony Messenger to be processed. ### Error handling AWS Lambda has error handling mechanisms (retrying and handling failed messages). Because of that, this package does not integrate Symfony Messenger's retry mechanism. Instead, it works with Lambda's retry mechanism. By default, Lambda will retry failed messages 2 times. ## Configuration ### Configuring AWS clients By default, AWS clients (SQS, SNS, EventBridge) are preconfigured to work on AWS Lambda (thanks to [environment variables populated by AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime)). However, it is possible to customize the AWS clients, for example to use them outside of AWS Lambda (locally, on EC2…) or to mock them in tests. These clients are registered as Symfony services under the keys: - `bref.messenger.sqs_client` - `bref.messenger.sns_client` - `bref.messenger.eventbridge_client` For example to customize the SQS client: ```yml services: bref.messenger.sqs_client: class: AsyncAws\Sqs\SqsClient public: true # the AWS clients must be public arguments: # Apply your own config here - region: us-east-1 ``` ### Automatic transport recognition Automatic transport recognition is primarily handled by default through TransportNameResolvers for SNS and SQS, ensuring that the transport name is automatically passed to your message handlers. However, in scenarios where you need to manually specify the transport name or adjust the default behavior, you can do so by setting the `$transportName` parameter in your service definitions within the config/services.yaml file. This parameter should match the transport name defined in your config/packages/messenger.yaml. For instance, for an `SnsConsumer`, you would configure it as follows: ```yaml # config/packages/messenger.yaml framework: messenger: transports: async: '%env(MESSENGER_TRANSPORT_DSN)%' ``` ```yaml # config/services.yaml services: Bref\Symfony\Messenger\Service\Sns\SnsConsumer: public: true autowire: true arguments: # Pass the transport name used in config/packages/messenger.yaml $transportName: 'async' ``` ### Disabling transports By default, this package registers Symfony Messenger transports for SQS, SNS and EventBridge. If you want to disable some transports (for example in case of conflict), you can remove `BrefMessengerBundle` from `config/bundles.php` and reconfigure the transports you want in your application's config. Take a look at [`Resources/config/services.yaml`](https://github.com/brefphp/symfony-messenger/blob/master/src/Resources/config/services.yaml) to copy the part that you want. ### Customizing the serializer If you want to change how messages are serialized, for example to use [Happyr message serializer](https://github.com/Happyr/message-serializer), you need to add the serializer on both the transport and the consumer. For example: ```yaml # config/packages/messenger.yaml framework: messenger: transports: async: dsn: 'https://sqs.us-east-1.amazonaws.com/123456789/my-queue' serializer: 'Happyr\MessageSerializer\Serializer' # config/services.yaml services: Bref\Symfony\Messenger\Service\Sqs\SqsConsumer: public: true autowire: true arguments: $serializer: '@Happyr\MessageSerializer\Serializer' ``` --- Source: https://bref.sh/docs/symfony/caching # Symfony Caching As explained in the [Storage documentation](https://bref.sh/docs/environment/storage), the filesystem is readonly on AWS Lambda except for `/tmp`. However, the `/tmp` directory isn't shared across Lambda instances. If your Lambda function scales up or is redeployed, the cache will be empty in new instances. If you want the cache to be shared across all Lambda instances, for example if your application caches a lot of data or if you use it for locking mechanisms (like API rate limiting), you can instead use Redis or DynamoDB. DynamoDB is the easiest to set up and is "pay per use". Redis is a bit more complex as it requires a VPC and managing instances, but offers slightly faster response times. ## DynamoDB Cache A Symfony bundle is available to use AWS DynamoDB as a cache store: [rikudou/psr6-dynamo-db-bundle](https://github.com/RikudouSage/DynamoDbCachePsr6Bundle). Install the bundle with: ```bash composer require rikudou/psr6-dynamo-db-bundle ``` Thanks to Symfony Flex, the bundle comes pre-configured to run in Lambda. Now, you can follow [this section of the documentation](https://bref.sh/docs/environment/storage#deploying-dynamodb-tables) to deploy your DynamoDB table using the Serverless Framework. --- Source: https://bref.sh/docs/symfony/keep-alive # Keeping the Symfony Kernel alive between requests > [!WARNING] > > This is an **advanced approach** aimed at optimizing performance. If you are just starting with Bref, this approach is not recommended because it is more complex to set up and can lead to unexpected issues. By default, Bref uses PHP-FPM to handle HTTP requests. This means that the Symfony Kernel is restarted for every request. This is not a problem for most applications, but if you want to optimize performance, you can keep the Symfony Kernel alive between requests. This approach is similar to [Laravel Octane](https://bref.sh/docs/laravel/octane), or running Symfony with [RoadRunner](https://roadrunner.dev/). ## Usage The Bref Symfony Bridge integrates with the Symfony Runtime component. This means that Bref can natively set the Symfony Kernel as the handler for Lambda functions, without going through PHP-FPM: ```diff filename="serverless.yml" functions: app: - handler: public/index.php + handler: App\Kernel # Switch from PHP-FPM to the "function" runtime: - runtime: php-84-fpm + runtime: php-84 environment: + # The Symfony process will restart every 100 requests + BREF_LOOP_MAX: 100 ``` The `App\Kernel` will be retrieved via Symfony Runtime from `public/index.php`. If you don't have a `public/index.php`, read the next sections. ## How it works Traditionally, Bref runs Symfony applications with the [PHP-FPM runtime](https://bref.sh/docs/runtimes/fpm-runtime). By switching to the [Function runtime](https://bref.sh/docs/runtimes/function), Bref loads the Symfony Kernel directly and can keep it alive between requests (controlled by `BREF_LOOP_MAX`). Note that the execution model of AWS Lambda is unchanged: the entire Lambda instance is frozen between requests. The Symfony Kernel is kept alive in memory, but it is not running. When a new request comes in, the Lambda instance is thawed and the request is handled. The main risks with this approach are memory leaks and global state. If your application has memory leaks, the memory usage will increase over time and eventually reach the Lambda limit. This can be mitigated by setting `BREF_LOOP_MAX` to a low value, so that the Symfony Kernel is restarted regularly. If your application uses global state, it will be shared between requests, which can be a disaster security-wise. ## Custom bootstrap file If you do not have a `public/index.php` file, you can create a file that returns the kernel (or any PSR-11 container): ```php } title="Get started with Laravel" arrow="true" href="https://bref.sh/docs/laravel/getting-started" /> } title="Get started with Symfony" arrow="true" href="https://bref.sh/docs/symfony/getting-started" /> ## Setup First, **follow the [Setup guide](https://bref.sh/docs/setup)** to create an AWS account and install the necessary tools. Next, in an empty directory, install Bref using Composer: ```bash composer require bref/bref ``` Make sure that the version of Bref that was installed is 3.0 or greater. Then, create a `serverless.yml` file. This file will describe how to deploy your application. ```yml filename="serverless.yml" service: app # your application name (lowercase without spaces) bref: # Uncomment and set your team ID if you are using Bref Cloud #team: bref-team-id provider: name: aws region: us-east-1 # AWS region to deploy to environment: # Environment variables APP_ENV: prod functions: web: # `index.php` is the entrypoint of your application handler: index.php runtime: php-84-fpm timeout: 28 # in seconds (API Gateway has a max timeout of 29 seconds) events: - httpApi: '*' package: patterns: # Exclude files from deployment - '!node_modules/**' - '!tests/**' plugins: - ./vendor/bref/bref ``` If your `index.php` entrypoint is in a different folder, feel free to adjust the `handler` key. For example if it is in `public/index.php`: ```yml handler: public/index.php ``` If this is a new application, you can create a very simple `index.php` file to test things out, for example: ```php ```bash serverless deploy ``` ```bash bref deploy ``` Once the command finishes, it should print a URL like this one: ```sh https://3pjp2yiw97.execute-api.us-east-1.amazonaws.com ``` Open this URL and you should see your application: `index.php` is running on Lambda! Congrats on creating your first serverless application 🎉 To learn more about deployments, head over to the [Deployment guide](https://bref.sh/docs/deploy). ## Troubleshooting In case your application is showing a blank page after being deployed, [have a look at the logs](https://bref.sh/docs/environment/logs). ## Website assets Have a look at the [Website guide](https://bref.sh/docs/use-cases/websites) to learn how to deploy a website with assets. --- Source: https://bref.sh/docs/default/cli-commands # CLI commands We can run CLI commands and scripts on AWS Lambda by deploying a "console" function with `serverless.yml`. ```yml functions: cli: handler: the-php-script-to-run.php runtime: php-84-console ``` The function uses the [Console runtime](https://bref.sh/docs/runtimes/console). To execute the script on Lambda, run the command below: ```bash serverless bref:cli ``` ```bash bref command ``` We can also pass arguments to the script: ```bash serverless bref:cli --args="extra command line arguments and options" ``` ```bash bref command "extra command line arguments and options" ``` Our script will be invoked inside AWS Lambda and the result will be printed to the console. To learn more, read [the "Console" guide](https://bref.sh/docs/runtimes/console). --- Source: https://bref.sh/docs/runtimes # PHP runtimes for AWS Lambda There is no native support for PHP on AWS Lambda. Instead, we can use third-party runtimes via [AWS Lambda *custom runtimes*](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html). **Bref provides open-source runtimes to run PHP on Lambda**. These PHP runtimes are distributed as AWS Lambda layers and Docker images. ## Bref runtimes Bref provides 3 PHP runtimes: - The "FPM" runtime, to run **web applications**. - The "function" runtime, to run **event-driven functions**. - The "console" runtime, to run **CLI commands**. These runtimes are used by the Laravel & Symfony framework integrations to run web applications, console/artisan commands, queues, and more. The runtimes are available as AWS Lambda layers that you can use (explained below). They are also published as Docker images so that you can run your applications locally (more on that later). ### PHP-FPM runtime for web apps Name: `php-85-fpm`, `php-84-fpm`, `php-83-fpm`, and `php-82-fpm`. This runtime uses PHP-FPM to run **web applications** on AWS Lambda, like on a traditional server. It's **the easiest to start with**: it works like traditional PHP hosting and is compatible with Symfony, Laravel, and other frameworks. [Learn more about the PHP-FPM runtime](https://bref.sh/docs/runtimes/runtimes/fpm-runtime). ### Event-driven functions Name: `php-85`, `php-84`, `php-83`, and `php-82`. AWS Lambda was initially created to run _functions_ (yes, functions of code) in the cloud. The Bref "function" runtime lets you create Lambda functions in PHP like with any other language. This runtime works great to create **event-driven microservices**. > [!TIP] > > If you are getting started, we highly recommend using the FPM runtime instead. It's "PHP as usual" (like on any server), with all the benefits of serverless (simplicity, scaling, etc.). [Learn more about the Function runtime](https://bref.sh/docs/runtimes/runtimes/function). ### Console Name: `php-85-console`, `php-84-console`, `php-83-console`, and `php-82-console`. This runtime lets you run CLI console commands on Lambda. For example, we can run the [Symfony Console](https://symfony.com/doc/master/components/console.html) or [Laravel Artisan](https://laravel.com/docs/artisan). [Learn more about the Console runtime](https://bref.sh/docs/runtimes/runtimes/console). ## Usage To use a runtime, set it on each function in `serverless.yml`: ```yml filename="serverless.yml" service: app provider: name: aws plugins: - ./vendor/bref/bref functions: hello: # ... runtime: php-84 # or: runtime: php-84-fpm # or: runtime: php-84-console ``` Bref currently provides runtimes for PHP 8.2, 8.3, 8.4, and 8.5: - `php-85` - `php-84` - `php-83` - `php-82` - `php-85-fpm` - `php-84-fpm` - `php-83-fpm` - `php-82-fpm` - `php-85-console` - `php-84-console` - `php-83-console` - `php-82-console` > [!TIP] > > `php-84` means PHP 8.4.\*. It is not possible to require a specific "patch" version. The latest Bref versions always aim to support the latest PHP versions, so upgrade via Composer frequently to keep PHP up to date. ### The Bref plugin for serverless.yml Make sure to always include the Bref plugin in your `serverless.yml` config: ```yml filename="serverless.yml" plugins: - ./vendor/bref/bref ``` This plugin is what makes `runtime: php-84` work (as well as other utilities). It is explained in more detail in the section below. ### ARM runtimes It is possible to run AWS Lambda functions on [ARM-based AWS Graviton processors](https://aws.amazon.com/blogs/aws/aws-lambda-functions-powered-by-aws-graviton2-processor-run-your-functions-on-arm-and-get-up-to-34-better-price-performance/). This is usually considered a way to reduce costs and improve performance. You can deploy to ARM by using the `arm64` architecture: ```yml filename="serverless.yml" {5} functions: api: handler: public/index.php runtime: php-84-fpm architecture: arm64 ``` The Bref plugin will detect that change and automatically use the Bref ARM Lambda layers. > [!NOTE] > > The `bref-extra-extensions` package is not available for ARM processors yet. ### AWS Lambda layers The `runtime: php-xxx` runtimes we use in `serverless.yml` are not _real_ AWS Lambda runtimes. Indeed, PHP is not supported natively on AWS Lambda. What the Bref plugin for `serverless.yml` (the one we include with `./vendor/bref/bref`) does is it automatically turns this: ```yaml functions: hello: # ... runtime: php-84 ``` into this: ```yaml functions: hello: # ... runtime: provided.al2023 layers: - 'arn:aws:lambda:us-east-1:873528684822:layer:php-84:21' ``` ☝️ `provided.al2023` [is the generic Linux environment for custom runtimes](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html#runtimes-custom-use), and the `layers` config points to Bref's AWS Lambda layers. Thanks to the Bref plugin, our `serverless.yml` is simpler. It also automatically adapts to the AWS region in use, and automatically points to the correct layer version. You can learn more about "layers" [in this page](https://bref.sh/docs/runtimes/runtimes/runtimes-details). If you want to reference AWS Lambda layers directly (instead of using the simpler `runtime: php-84` syntax), the Bref plugin also provides simple `serverless.yml` variables. These were the default in Bref v1.x, so you may find this older syntax on tutorials and blog posts: ```yaml service: app provider: name: aws runtime: provided.al2023 plugins: - ./vendor/bref/bref functions: hello: # ... layers: - ${bref:layer.php-84} # or: - ${bref:layer.php-84-fpm} ``` The `${...}` notation is the [syntax to use variables](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/variables.md) in `serverless.yml`. The Bref plugin provides the following variables: - `${bref:layer.php-85}` - `${bref:layer.php-84}` - `${bref:layer.php-83}` - `${bref:layer.php-82}` - `${bref:layer.php-85-fpm}` - `${bref:layer.php-84-fpm}` - `${bref:layer.php-83-fpm}` - `${bref:layer.php-82-fpm}` - `${bref:layer.console}` Bref ARM layers are the same as the x86 layers, but with the `arm-` prefix in their name, for example `${bref:layer.arm-php-82}`. The only exception is `${bref:layer.console}` (this is the same layer for both x86 and ARM). > [!TIP] > > To be clear, it is easier and recommended to use the `runtime: php-xxx` option instead of setting `layers` directly. --- Source: https://bref.sh/docs/runtimes/fpm-runtime # PHP-FPM runtime for AWS Lambda To run HTTP APIs and websites on AWS Lambda, Bref runs your code **using PHP-FPM**. That means PHP applications can run on Lambda just like on any other PHP hosting platform. That's great: we can use our favorite framework as usual, like **Laravel or Symfony**. > [!TIP] > > Every code we deploy on AWS Lambda is called a "Function". Do not let this name confuse you: we do deploy HTTP **applications** in a Lambda Function. > > In the Lambda world, an HTTP application is a *function* that is called by a request and returns a response. Good news: this is exactly what our PHP applications do. ## How it works AWS Lambda can react to HTTP requests via [API Gateway](https://aws.amazon.com/api-gateway/). On Lambda, the Bref runtime starts PHP-FPM and forwards API Gateway requests to PHP-FPM via the FastCGI protocol. ![](https://bref.sh/docs/runtimes/fpm-runtime.png) Bref is basically doing the same thing as Apache or Nginx, and the PHP code runs in the same environment as on any server. While this may sound like a lot to deploy an entire application inside Lambda, it works very well. Performance-wise, the overhead of the Bref runtime with PHP-FPM is less than a millisecond. Many users and companies have been running websites and HTTP APIs on Lambda at scale for years. ## Usage To deploy an HTTP application on AWS Lambda, use the `php-xx-fpm` runtime and combine it with the `httpApi` event: ```yml filename="serverless.yml" {9-11} service: app provider: name: aws plugins: - ./vendor/bref/bref functions: app: handler: index.php runtime: php-84-fpm events: - httpApi: '*' ``` The `httpApi` event will deploy an API Gateway HTTP API and the `*` configuration will forward all requests to your PHP application. ### Handler The *handler* is the file that is invoked when an HTTP request comes in. It is the same file that is traditionally configured in Apache or Nginx. In Symfony and Laravel this is usually `public/index.php` but it can be anything. ```yml filename="serverless.yml" functions: app: handler: public/index.php ``` ## Context access ### Lambda context Lambda provides information about the invocation, function, and execution environment via the *lambda context*. Bref exposes the Lambda context in the `$_SERVER['LAMBDA_INVOCATION_CONTEXT']` variable as a JSON-encoded string. Here is an example to retrieve it: ```php $lambdaContext = json_decode($_SERVER['LAMBDA_INVOCATION_CONTEXT'], true); ``` ### Request context API Gateway integrations can add information to the HTTP request via the *request context*. This is the case, for example, when using AWS Cognito authentication on API Gateway. Bref exposes the request context in the `$_SERVER['LAMBDA_REQUEST_CONTEXT']` variable as a JSON-encoded string. Here is an example to retrieve it: ```php $requestContext = json_decode($_SERVER['LAMBDA_REQUEST_CONTEXT'], true); ``` --- Source: https://bref.sh/docs/runtimes/function # PHP functions runtime for AWS Lambda Bref's **"Event-driven function" runtime** lets you run PHP functions on AWS Lambda. Unlike the [PHP-FPM runtime](https://bref.sh/docs/runtimes/fpm-runtime), the function runtime does not use PHP-FPM. Instead, it invokes your PHP code directly with the AWS Lambda event. Here is an example of a PHP Lambda function written as an anonymous function: ```php [!TIP] > > If you are creating HTTP applications, the [PHP-FPM runtime](https://bref.sh/docs/runtimes/fpm-runtime) is a simpler option. ## Usage To deploy a PHP function on AWS Lambda, use the `php-xx` runtime: ```yaml service: app provider: name: aws plugins: - ./vendor/bref/bref functions: hello: handler: my-function.php runtime: php-84 ``` ## PHP functions Functions that can run on Lambda can be an anonymous function or [any kind of callable supported by PHP](https://www.php.net/manual/en/language.types.callable.php). ```php Set the class name as the `handler` and Bref will retrieve that class from Laravel's service container. ```yml filename="serverless.yml" functions: hello: handler: MyApp\Handler ``` Set the class name as the `handler` and Bref will retrieve that class from Symfony's service container. ```yml filename="serverless.yml" functions: hello: handler: MyApp\Handler ``` To achieve that, you must integrate Bref with your framework's Dependency Injection Container. First, create a file (for example `init.php`) that calls `Bref::setContainer()`: ```php ## Invocation A PHP function must be invoked via the AWS Lambda API, either manually or by integrating with other AWS services. > [!TIP] > > If you instead want to write a classic **HTTP application**, use the [PHP-FPM runtime](https://bref.sh/docs/runtimes/fpm-runtime) instead. ### CLI A PHP function can be triggered manually from the CLI using the [`serverless invoke` command](https://github.com/oss-serverless/osls/blob/4.x/docs/cli-reference/invoke.md): ```bash $ serverless invoke -f # The function name is the one in serverless.yml, in our example that would be `hello`: $ serverless invoke -f hello "Hello world" ``` To pass event data to the lambda use the `--data` option. For example: ```bash serverless invoke -f --data='{"name": "John" }' ``` Run `serverless invoke --help` to learn more about the `invoke` command. ### From PHP applications A PHP function can be triggered from another PHP application using the AWS PHP SDK: You first need to install the AWS PHP SDK by running ```bash $ composer require aws/aws-sdk-php ``` > [!NOTE] > > The `aws/aws-sdk-php` package is very large (100MB+). To avoid hitting Lambda's 250MB size limit, [remove unused AWS services from the package](https://bref.sh/docs/deploy#reducing-package-size). ```php $lambda = new \Aws\Lambda\LambdaClient([ 'version' => 'latest', 'region' => '', ]); $result = $lambda->invoke([ 'FunctionName' => '', 'InvocationType' => 'RequestResponse', 'LogType' => 'None', 'Payload' => json_encode(/* event data */), ]); $result = json_decode($result->get('Payload')->getContents(), true); ``` > [!WARNING] > > When invoking Lambda functions, use the exact function name as it appears in the AWS console, not the function name from `serverless.yml`. For example, if your service is named `my-app` and your function is named `hello`, the actual Lambda function name will be `my-app-dev-hello` (or similar, depending on your stage). > [!NOTE] > > A lighter alternative to the official AWS PHP SDK is the [AsyncAws Lambda](https://async-aws.com/clients/lambda.html) package. ### From other AWS services Functions are perfect to react to events emitted by other AWS services. For example, you can write code that processes new SQS events, SNS messages, new uploaded files on S3, DynamoDb insert and update events, etc. Plenty of examples are available in the "Use cases" section in the menu, get started there! --- Source: https://bref.sh/docs/runtimes/console # Console runtime Bref's "Console" runtime lets us run CLI scripts on AWS Lambda. This can be used to run PHP scripts, like cron tasks, the [Symfony Console](https://symfony.com/doc/current/console.html), [Laravel Artisan](https://laravel.com/docs/artisan), and more. ## How it works When the function is invoked, the Console runtime will execute the PHP script defined as the *handler* in a sub-process. The result of the execution (exit code and output) will be returned as the result of the AWS Lambda invocation. All the CLI output is also logged ([learn more about logs](https://bref.sh/docs/environment/logs)). Console functions can be invoked: - via a cron schedule - via the `serverless bref:cli` command - via the `bref command` command when using [Bref Cloud](https://bref.sh/cloud) - manually by invoking the function via the AWS API ## Usage The Lambda function used for running console applications must use the `php-xx-console` runtime. Here is an example: ```yml filename="serverless.yml" {8-9} service: app provider: name: aws plugins: - ./vendor/bref/bref functions: hello: handler: the-php-script-to-run.php runtime: php-84-console ``` Behind the scenes, the `php-xx-console` runtime will deploy a Lambda function configured to use Bref's `php-84` AWS Lambda layer plus Bref's `console` layer (read more about these in the [runtimes documentation](https://bref.sh/docs/runtimes)). ## Running commands When invoked, the "Console" runtime executes the `handler` script in a sub-process. For example, if the following handler was defined: ```yml filename="serverless.yml" functions: hello: handler: the-php-script-to-run.php runtime: php-84-console ``` Then the following command would run in Lambda every time the function is invoked: ```sh php the-php-script-to-run.php ``` The Lambda function can be invoked with a payload. It must be a JSON string, for example `"arg1 arg2 --option1=foo"`. Note that it is a string encoded in JSON, that is why it is in quotes, `json_decode($payload)` would return the string itself. In our example, the following command would run in Lambda when invoked with such a payload: ```sh php the-php-script-to-run.php arg1 arg2 --option1=foo ``` ### Cron Read the dedicated documentation for [running cron tasks on AWS Lambda](https://bref.sh/docs/use-cases/cron). ### CLI invocation To manually run a console command on AWS Lambda, run the following command on your computer: ```bash serverless bref:cli --args="{arguments and options for the script}" ``` The `bref:cli` command will automatically detect which function (in `serverless.yml`) uses the `console` runtime and will run the command on that function. Pass your command arguments and options in the `--args` flag (shortcut: `-a`). Remember to escape quotes properly. Some examples: ```bash # Runs the CLI application without arguments and displays the help $ serverless bref:cli # ... $ serverless bref:cli --args="doctrine:migrations:migrate" Your database will be migrated. To execute the SQL queries run the command with the `--force` option. $ serverless bref:cli -a "doctrine:migrations:migrate --force" Your database has been migrated. $ serverless bref:cli --stage=prod -a "db:dump --file='/tmp/dump.sql' --verbose" # ... # You can use environment variables to configure AWS credentials (e.g. in CI) $ AWS_ACCESS_KEY_ID=foo AWS_SECRET_ACCESS_KEY=bar serverless bref:cli # ... ``` The `bref:cli` command can be used to run CLI commands in Lambda from your machine, but can also be used in CI/CD to run DB migrations for example. ```bash bref command "{arguments and options for the script}" ``` It will automatically detect which function (in `serverless.yml`) uses the `console` runtime and will run the command on that function. Pass your command arguments and options as a single string. Remember to escape quotes properly. Some examples: ```bash # Runs the CLI application without arguments and displays the help $ bref command # ... $ bref command "doctrine:migrations:migrate" Your database will be migrated. To execute the SQL queries run the command with the `--force` option. $ bref command "doctrine:migrations:migrate --force" Your database has been migrated. $ bref command --env=prod "db:dump --file='/tmp/dump.sql' --verbose" # ... ``` The `bref command` command can be used to run CLI commands in Lambda from your machine, but can also be used in CI/CD to run DB migrations for example. ### From the Bref Cloud dashboard As an alternative to the CLI, [Bref Cloud](https://bref.sh/cloud) lets you run commands on a deployed environment directly from the dashboard. Functions using the "console" runtime are automatically detected, and colors are enabled by default for Laravel Artisan and Symfony Console. ### Without Serverless Framework If you do not use `serverless.yml` but something else, like SAM/AWS CDK/Terraform, you can invoke your console function via the AWS CLI. For example: ```bash aws lambda invoke \ --function-name \ --region \ --cli-binary-format raw-in-base64-out \ --payload '""' \ .json # For example: aws lambda invoke \ --function-name myapp-dev-myfunction \ --region us-east-1 \ --cli-binary-format raw-in-base64-out \ --payload '"doctrine:migrations:migrate --force"' \ response.json # To extract the command output from the response.json file using jq # https://stedolan.github.io/jq/ aws lambda invoke \ --function-name myapp-dev-myfunction \ --region us-east-1 \ --cli-binary-format raw-in-base64-out \ --payload '"doctrine:migrations:migrate --force"' \ response.json && jq -r .output response.json ``` > **Note:** > The `--payload` needs to contain a JSON string, that is why it is quoted twice: `'"..."'`. This is intentional. ## Lambda context Lambda provides information about the invocation, function, and execution environment via the *lambda context*. This context is usually available as a parameter (alongside the event), within the defined handler. However, within the console runtime we do not have direct access to this parameter. To work around that, Bref puts the Lambda context in the `$_SERVER['LAMBDA_INVOCATION_CONTEXT']` variable as a JSON-encoded string. ```php $lambdaContext = json_decode($_SERVER['LAMBDA_INVOCATION_CONTEXT'], true); ``` --- Source: https://bref.sh/docs/runtimes/runtimes-details # Runtimes in detail > [!NOTE] > > This section is only useful if you want to learn more, or if you want to use Bref with a deployment tool other than Serverless Framework. > > You can skip it for now if you just want to get started with Bref. ## AWS Lambda layers Bref runtimes are distributed as [AWS Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html). While Bref provides a Serverless plugin to simplify how to use them, you can use the layers directly. The layer names (aka "[ARN](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html)") follow this pattern: ``` arn:aws:lambda::873528684822:layer:: ``` For example: ``` arn:aws:lambda:us-east-1:873528684822:layer:php-84:21 ``` You can use layers via their full ARN, for example in `serverless.yml`: ```yml filename="serverless.yml" {9} service: app provider: name: aws functions: hello: # ... runtime: provided.al2023 layers: - 'arn:aws:lambda:us-east-1:873528684822:layer:php-84:21' ``` Or if you are using [SAM's `template.yaml`](https://aws.amazon.com/serverless/sam/): ```yml filename="template.yml" AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Resources: Hello: Type: AWS::Serverless::Function Properties: # ... Runtime: provided.al2023 Layers: - 'arn:aws:lambda:us-east-1:873528684822:layer:php-84:21' ``` Bref layers work with AWS Lambda regardless of the tool you use to deploy your application: Serverless, SAM, CloudFormation, Terraform, AWS CDK, etc. > [!WARNING] > > Remember: the layer ARN contains a region. **You need to use the same region as the rest of your application** else Lambda will not find the layer. ## Layer versions All the layer/runtime versions can be found at [**runtimes.bref.sh**](https://runtimes.bref.sh/). Here are the latest versions: You can also find the appropriate ARN/version for your current Bref version by running: ```bash serverless bref:layers ``` > [!WARNING] > > If you use a layer ARN directly, you will need to update the ARN regularly (the `version` part). ### Layers NPM package You can use [the `@bref.sh/layers.js` NPM package](https://github.com/brefphp/layers.js) to get up-to-date layer ARNs in Node applications, for example with the AWS CDK. ## Environment variables Bref exposes the following environment variables on every Lambda invocation: - **`LAMBDA_REQUEST_ID`**: the current AWS Lambda request ID. This is useful for logging and tracing purposes, for example to group all logs of the same invocation. - **`_X_AMZN_TRACE_ID`**: the [AWS X-Ray](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html) trace ID. This is a standard AWS Lambda variable that is normally set by native runtimes. These variables are refreshed on every invocation. ## Telemetry ping Bref layers send an anonymous ping to estimate the total number of Lambda invocations powered by Bref. That statistic is useful in two ways: - to provide new users an idea on how much Bref is used in production - to communicate to AWS how much Bref is used and push for better PHP integration with AWS Lambda tooling We consider this to be beneficial both to the Bref project (to get more users and more consideration from AWS) and for Bref users (more users means a larger community, a stronger and more active project, as well as more features from AWS). So far, knowing the number of Bref invocations has helped Bref grow, be recognized by AWS, and opened a lot of doors. ### Data You can find the latest number of AWS Lambda invocations on the [Bref homepage](https://bref.sh/). On the month of the Bref 1.0 release, [Bref was powering 1 billion invocations per month](https://bref.sh/news/01-bref-1.0#1-billion-executions-per-month). On the month of the Bref 2.0 release, [Bref was powering 10 billion invocations per month](https://bref.sh/news/02-bref-2.0). On the month of the Bref 3.0 release, [Bref was powering 40 billion invocations per month](https://bref.sh/news/03-bref-3.0). ### What is sent The data sent in the ping is completely anonymous. It does not contain any identifiable data about anything (the project, users, etc.). **The only data it contains is:** A Bref invocation happened with the runtime XYZ (where XYZ is the name of the Bref runtime, like "function", "fpm" or "console") and whether the invocation was a cold start. Here is an example payload: ``` Invocations_100:1|c\nLayer_fpm_100:1|c\nCold_100:1\nWarm_100:0|c ``` Anyone can inspect the code and the data sent by checking the [`Bref\Runtime\LambdaRuntime::ping()` function](https://github.com/brefphp/bref/blob/master/src/Runtime/LambdaRuntime.php#L387). ### How is it sent The data is sent via the [statsd](https://github.com/statsd/statsd) protocol, over [UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol). Unlike TCP, UDP does not check that the message correctly arrived at the server. It doesn't even establish a connection. That means that **UDP is extremely fast**: the data is sent over the network and the code moves on to the next line. When actually sending data, the overhead of that ping takes about 150 micro-seconds. However, this function actually sends data every 100 invocations, because we don't need to measure *all* invocations. We only need an approximation. That means that 99% of the time, no data is sent, and the function takes 30 micro-seconds. If we average all executions, the overhead of that ping is about **31 micro-seconds**. Given that it is much much less than even 1 milli-second, we consider that overhead negligible. ### Disabling The ping can be disabled by setting a `BREF_PING_DISABLE` environment variable to `1`. > [!TIP] > > If your company policy requires disabling the ping, it would be extremely beneficial to the project (and transitively to your company) to report privately a rough number of invocations. You can send me a direct email at [matthieu@bref.sh](mailto:matthieu@bref.sh). > > Such data is collected privately and is not shared publicly on its own. I include it in the total number of invocations that I share publicly. --- Source: https://bref.sh/docs/how-it-works # How Bref and AWS Lambda work Bref runs PHP on AWS Lambda. This video explains in detail how it works. Note that understanding the low-level details is not necessary to use Bref. This video is for those who are curious about how Bref works under the hood. --- Source: https://bref.sh/docs/serverless-costs # Costs of a serverless application Unlike traditional hosting, serverless hosting is billed based on usage. This means that you only pay for what you use, **down to the request**. To be clear, this means that if your application is not used, you don't pay anything. On AWS Lambda, you pay for: - the number of requests - the duration of the requests (the time it takes to execute your code) There are no costs when the PHP application is waiting between requests (or jobs, events, etc.). It doesn't matter if AWS Lambda scaled your functions up to several instances (containers), you only pay for the requests and the duration of the requests. For some use cases it has interesting consequences: 1 job running for 10 minutes has about the same costs as 600 jobs running for 1 second in parallel. ## Costs calculator Use the calculator below to estimate the costs of running your PHP application serverless on AWS Lambda. --- Source: https://bref.sh/docs/vendor-lock-in # AWS Lambda vendor lock-in A key design goal of Bref is to make it easy to port existing applications to AWS Lambda without changes. In most scenarios, **your PHP code does not need to be adapted for AWS Lambda**. ## No vendor lock-in Bref provides abstractions that make your code run like it would on any traditional server: the [default runtime](https://bref.sh/docs/runtimes/fpm-runtime) runs PHP-FPM exactly like on a classic server, framework integrations preconfigure common needs like file storage and queues, and console commands run as usual through the [Console runtime](https://bref.sh/docs/runtimes/console). **New projects with modern frameworks often don't require code changes**. For existing (or legacy) applications, the main changes are usually about **making applications "cloud-ready"**. This means preparing your code to run distributed across multiple servers: - Storing sessions in a central place (e.g. the database or Redis) instead of the local filesystem - Sending logs to a central place (AWS CloudWatch) instead of local files - Using centralized caching (e.g. Redis) instead of local file caching - Storing files on cloud storage (e.g. S3) instead of the local filesystem - Hosting the database on a separate server (e.g. RDS) instead of using SQLite or running MySQL on the same server as the application The good news is: **these changes are not specific to serverless or AWS Lambda**. They are necessary for any application that needs to scale across multiple servers. This work is not wasted. **This doesn't create vendor lock-in.** This actually helps move to another hosting solution if you ever need to. The reason this is made easy is a combination of both native framework features and Bref's integrations, for example: - Frameworks can easily switch between different storage and caching backends - Bref preconfigures logs to be sent to CloudWatch - Bref provides SQS + Lambda runtime integrations for Symfony Messenger and Laravel Queues ## Choosing vendor lock-in Vendor lock-in is not a bad thing in itself. It's a trade-off. While Bref makes things easy, it also allows you to take advantage of all that AWS and AWS Lambda have to offer. That means **you can make the choice of coupling to AWS Lambda's lower-level features if you want to**. For example, you can write event-driven functions to handle events from other AWS services (you will find plenty of examples in the "Use cases" section of the docs), as well as skip the framework integrations and write custom [SQS queue handlers](https://bref.sh/docs/use-cases/sqs) (for example to optimize performance or costs). This approach is completely optional: you can start with a traditional PHP application structure and gradually adopt AWS features if and when they provide value. --- Source: https://bref.sh/docs/deploy # Deployment Bref is designed out of the box to deploy using [`serverless.yml`](https://github.com/oss-serverless/osls). Bref can also work with any other deployment tool: Terraform, CloudFormation, SAM, [AWS CDK](https://github.com/brefphp/constructs), Pulumi… However, the documentation and user experience are optimized for `serverless.yml`. ## Deploying manually To deploy to AWS an application configured with `serverless.yml`, run: ```bash serverless deploy ``` ```bash bref deploy ``` A `.serverless/` directory will be created. You can add it to `.gitignore`. ## Deploying for production In the previous step, we deployed the project installed on your machine. This is probably a *development version*. For production, we usually don't want to deploy: - dev dependencies - dev configuration - etc. Instead, let's remove development dependencies and optimize Composer's autoloader for production: ```bash composer install --prefer-dist --optimize-autoloader --no-dev ``` Now is also the best time to configure your project for production, as well as build any file cache if necessary. Once your project is ready, you can deploy via the following command: ```bash serverless deploy ``` ```bash bref deploy ``` ### Reducing package size AWS Lambda has a **250MB size limit** for deployed applications (unzipped). Large Composer dependencies can push your project over this limit and also lead to slower cold starts. The most common offender is `aws/aws-sdk-php`: it ships clients for **every** AWS service, adding over 100MB to your vendor directory. If you only use a few services (e.g. S3, SQS, DynamoDB), you can remove the rest. Add the following to your `composer.json`: ```json { "scripts": { "pre-autoload-dump": [ "Aws\\Script\\Composer\\Composer::removeUnusedServices" ] }, "extra": { "aws/aws-sdk-php": [ "Lambda", "S3", "Sqs", "DynamoDb" ] } } ``` Then run `composer install` (or `composer update`) to apply the changes. Adjust the list to keep only the AWS services your application actually uses. Note: `S3`, `Kms`, `SSO`, and `Sts` cannot be removed as they are required by the SDK core. [Read more in the `aws/aws-sdk-php` documentation.](https://github.com/aws/aws-sdk-php/tree/master/src/Script/Composer) > [!NOTE] > > As an alternative, you can replace `aws/aws-sdk-php` with [AsyncAws](https://async-aws.com/), a lighter SDK that only installs the services you need. Using `google/apiclient`? A similar approach is available: [see the documentation to remove unused Google API services](https://github.com/googleapis/google-api-php-client#cleaning-up-unused-services). To further reduce deployment size, exclude non-essential files (tests, assets, node_modules) from the package. [Read more in the serverless.yml exclusions documentation.](https://bref.sh/docs/deploy/environment/serverless-yml#exclusions) If your application still exceeds the 250MB limit, you can [deploy via Docker images instead](https://bref.sh/docs/deploy/deploy/docker). ## Environments We can deploy the same application multiple times in completely separated environments (also called "stages" by the Serverless CLI). ```bash serverless deploy --stage=prod ``` ```bash bref deploy --env=prod # or bref deploy -e prod ``` The default environment is `dev`. The example above deploys a `prod` environment. Each environment is a separate CloudFormation stack, with completely separate AWS resources (Lambda functions, logs, permissions, etc.). All AWS resources are prefixed with the `service` and environment name (for example `myapp-dev-api`), which avoids any collision between environments. It is possible to deploy different environments in different AWS accounts (to lock down permissions), and to deploy one environment per git branch, pull request, or even developer in the team. ## Automating deployments ### Bref Cloud If you are using [Bref Cloud](https://bref.sh/cloud), you can easily set up automatic deployments from CI/CD tools. Read the [documentation on deploying with Bref Cloud](https://bref.sh/docs/deploy/cloud-deploy) for more information. ### Serverless CLI If you are using GitHub Actions, GitLab CI, CircleCI, or any tool of the sort, you will want to automate the deployment to something like this: ```bash # Install Composer dependencies optimized for production composer install --prefer-dist --optimize-autoloader --no-dev # Perform extra tasks for your framework of choice # (e.g. generate the framework cache) # [...] # Deploy serverless deploy ``` That will also mean creating AWS access keys so that the continuous integration is allowed to deploy. You can find configuration examples for CI/CD tools in the [Bref examples repository](https://github.com/brefphp/examples). ## Regions AWS runs applications in different [regions](https://aws.amazon.com/about-aws/global-infrastructure/). The default region is `us-east-1` (North Virginia, USA). If you want to use a different region (for example to host your application closer to your visitors) you can configure it in your `serverless.yml`: ```yaml provider: region: eu-west-1 # Ireland, Europe ... ``` > [!TIP] > > If you are a first time user, using the `us-east-1` region (the default region) is recommended for the first projects. It simplifies commands and avoids a lot of mistakes when discovering AWS. ## Deletion You can delete a deployed environment using the `remove` command. ```bash serverless remove # or remove a specific environment serverless remove --stage=prod ``` Note that because of the way Serverless Framework works, you will need to delete the contents of AWS S3 buckets manually before running this command. ```bash bref remove # or remove a specific environment bref remove --env=prod ``` Bref Cloud will automatically delete the contents of AWS S3 buckets. **Deleting an environment destroys the AWS resources that were created for that environment.** If you want to delete all environments of an application, you can do so in the [Bref Cloud dashboard](https://bref.cloud). If you don't use Bref Cloud, you will need to delete each environment one by one. ## How it works ### CloudFormation stacks Under the hood, Bref will deploy everything to AWS as a **[CloudFormation](https://aws.amazon.com/cloudformation/) stack**. A "stack" is nothing more than a bunch of things that compose an application: - Lambda functions - HTTP endpoints - S3 buckets - databases - etc. Stacks make it easy to group those resources together: the whole stack is updated at once on deployments, and if you delete the stack all the resources inside are deleted together too. Clean and simple. ### Zero-downtime deployments CloudFormation deploys using the [blue/green deployment strategy](https://docs.aws.amazon.com/whitepapers/latest/overview-deployment-options/bluegreen-deployments.html). This means that when you deploy, a new version of your code is deployed alongside the old one. Once the new version is ready, the traffic switches to the new version. If the deployment fails at any point, the traffic stays on the old version and the deployment is rolled back. #### Limits to blue/green deployment As soon as you introduce **asynchronous behaviors** (e.g. background jobs with SQS, event-driven microservices…) you may have in-flight messages (SQS jobs, EventBridge events…) created by the old version of your code that will be processed by the new version of your code. Code that handles asynchronous events must be able to handle messages created by older versions of the code. #### Database migrations Zero-downtime deployments mean that database migrations must run when code is running in production. That means either before or after the deployment (traffic switch) happens, and having a DB migration strategy compatible with that. ### `serverless.yml` Serverless Framework offers a simple configuration format. This is what you are using if you use Bref. That configuration is written in your project in a `serverless.yml` file. You can [learn more about that configuration format here](https://bref.sh/docs/deploy/environment/serverless-yml). ## Learn more Read more about `serverless deploy` in [the official documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/deploying.md). --- Source: https://bref.sh/docs/deploy/docker # Deploying container images > [!TIP] > > Are you starting with Bref? Deploy **without Docker first**. It's easier and faster. You can always switch to Docker later. > > [Read the "Deployment" guide](https://bref.sh/docs/deploy) By default, Bref deploys to AWS Lambda using zip archives, which Lambda will run in an Amazon Linux environment. This is how AWS Lambda works out of the box, and it works great. However, AWS Lambda also supports **deploying and running container images** (aka Docker images). We don't recommend starting out with containers, as it is less practical and requires some knowledge of Docker. Yes, Docker is great and probably sounds familiar, but is often not worth it on Lambda. You should consider deploying using Docker when: - Your code size is [larger than the 250MB limit when unzipped](https://bref.sh/docs/environment/storage) - You reached the limit of 5 Lambda layers (e.g. for extra PHP extensions) - You need custom binaries/resources installed locally (e.g. mysqldump, wkhtmltopdf) > [!NOTE] > > This documentation page assumes that you have familiarized yourself with Bref first. ## Docker Image Bref helps you deploy using Docker images by offering base images that work on AWS Lambda. Here is an example of a Dockerfile you can use: ```dockerfile filename="Dockerfile" FROM bref/php-84:3 # Copy the source code in the image COPY . /var/task # In this example we're serving an HTTP application with php-fpm ENV BREF_RUNTIME=fpm # Configure the handler file (the entrypoint that receives all HTTP requests) CMD ["public/index.php"] ``` The `CMD` instruction lets us specify the entrypoint that will handle all requests. This is the equivalent of the `handler` in the `serverless.yml` file. > [!TIP] > > Always specify the major version of the Bref image you want to use. That avoids breaking changes when a new major version is released. > > For example `bref/php-84:3` points to Bref v3. The `ENV BREF_RUNTIME=fpm` lets us specify which [runtime](https://bref.sh/docs/runtimes) to use. As a reminder, Bref offers the following runtimes: - `fpm`: [uses PHP-FPM to serve web applications](https://bref.sh/docs/runtimes/fpm-runtime) - `function`: [to run PHP code](https://bref.sh/docs/runtimes/function) - `console`: [to run CLI commands](https://bref.sh/docs/runtimes/console) > [!WARNING] > > The `CMD` instruction in `Dockerfile` must contain a valid JSON array. This is why you must escape any `\` character. This is important for PHP class names, for example when using Laravel Octane: > > ```dockerfile filename="Dockerfile" > CMD ["Bref\\LaravelBridge\\Http\\OctaneHandler"] > ``` ### Extra PHP extensions You can enable additional PHP extensions by pulling them from [Bref Extra Extensions](https://github.com/brefphp/extra-php-extensions): ```dockerfile filename="Dockerfile" {3-4} FROM bref/php-84:3 COPY --from=bref/extra-gmp-php-84:3 /opt /opt COPY . /var/task CMD ["public/index.php"] ``` > [!TIP] > > Like the Bref images, always specify the major version of the Bref Extra Extensions images: `bref/extra-*:3` points to Bref Extra Extensions v3. > > Note that Bref v3 is compatible with Bref Extra Extensions v3. ## Deployment The Serverless Framework supports deploying Docker images to Lambda: ```yml filename="serverless.yml" {5-9,13-14} service: bref-with-docker provider: name: aws ecr: images: hello-world: # Path to the `Dockerfile` file path: ./ file: Dockerfile platform: linux/amd64 functions: hello: image: name: hello-world events: - httpApi: '*' ``` Instead of having a `handler` and a `runtime`, we'll declare an `image`. In the `provider` block, we'll declare the Docker images that we want to build and deploy. When running `serverless deploy`, the CLI will: - Build the Docker images according to their specified `path` - Create an AWS ECR repository called `serverless-{service}-{env}` - Authenticate against your ECR account - Push the newly built Docker image - Deploy the Lambda function pointing to the Docker image ### Using one image for all functions (recommended) A major benefit of deploying with Docker is that you can use **a single Docker image** for all your functions (web, console, queues, etc.). Instead of setting `BREF_RUNTIME` in the Dockerfile, set it per function via the `environment` variables in `serverless.yml`: ```dockerfile filename="Dockerfile" FROM bref/php-84:3 COPY . /var/task # No BREF_RUNTIME or CMD set here ``` ```yml filename="serverless.yml" {5-9,15,21,27} provider: name: aws ecr: images: my-app: path: ./ file: Dockerfile platform: linux/amd64 functions: web: image: name: my-app command: public/index.php # a PHP file path or class name environment: BREF_RUNTIME: fpm events: - httpApi: '*' console: image: name: my-app command: bin/console # an executable file path environment: BREF_RUNTIME: console worker: image: name: my-app command: Acme\QueueWorker # a PHP file path or class name environment: BREF_RUNTIME: function events: - sqs: arn: !GetAtt MyQueue.Arn ``` This simplifies your deployment pipeline: a single Docker image is built and pushed, and each function uses the appropriate runtime. You can read the detailed documentation about [all options available in the Serverless documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/functions.md#referencing-container-image-as-a-target). ## Filesystem Like with non-Docker deployments, the filesystem for Docker on AWS Lambda is also readonly with a limited disk space under `/tmp` for read/write. The `/tmp` folder will always be empty on cold starts. Avoid writing content to `/tmp` in your Dockerfile because that content will **not be available** for your Lambda function. [Read more about file storage in Lambda](https://bref.sh/docs/environment/storage). ## Docker Registry AWS Lambda only supports AWS ECR as the source location for Docker images. AWS Lambda will use the image digest as the unique identifier. This means that even if you overwrite the exact same tag on ECR, your lambda will still run the previous image code until you actually redeploy using the new image. --- Source: https://bref.sh/docs/deploy/aws-cdk # AWS CDK constructs for PHP on AWS Lambda The [Bref](https://bref.sh/) CDK constructs let you deploy serverless PHP applications on AWS Lambda using the AWS CDK. By default, Bref [deploys using the Serverless Framework](https://bref.sh/docs/deploy.html). Using the AWS CDK is an alternative, but be aware that this is an advanced topic. If you are lost, follow the [Bref documentation](https://bref.sh/docs/) instead. ## Installation Install [the package](https://github.com/brefphp/constructs) with NPM: ```bash npm install @bref.sh/constructs ``` ## Usage Simple example to deploy an HTTP application: ```typescript import { Construct } from 'constructs'; import { App, Stack } from 'aws-cdk-lib'; import { PhpFpmFunction } from '@bref.sh/constructs'; class MyStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); new PhpFpmFunction(this, 'Hello', { handler: 'public/index.php', }); } } const app = new App(); new MyStack(app, 'test', { env: { region: 'eu-west-1', }, }); ``` ## Constructs ### Functions #### `PhpFpmFunction` This construct deploys a PHP function with the [HTTP runtime](https://bref.sh/docs/runtimes/http.html). ```typescript new PhpFpmFunction(this, 'MyFunction', { handler: 'public/index.php', }); ``` It inherits from the AWS CDK [`Function` construct](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.Function.html) with these options set by default: - `handler`: `index.php` by default - `runtime`: `provided.al2023` - `code`: the code is automatically zipped from the current directory. - `layers`: the Bref layer is automatically added. - `memorySize`: `1024` - `timeout`: `28` (seconds) The code is automatically zipped from the current directory. You can override this behavior by setting the `code` property: ```typescript import { packagePhpCode } from '@bref.sh/constructs'; new PhpFpmFunction(this, 'MyFunction', { code: packagePhpCode('custom-path', { exclude: ['docs'], }), }); ``` The following paths are always excluded: `.git`, `.idea`, `cdk.out`, `node_modules`, `.bref`, `.serverless`, `tests`. The construct also adds the following options: - `phpVersion` (default: `8.4`): the PHP version to use. #### `PhpFunction` This construct deploys a PHP function with the ["event-driven function" runtime](https://bref.sh/docs/runtimes/function.html). ```typescript new PhpFunction(this, 'MyFunction', { handler: 'my-handler.php', }); ``` It inherits from the AWS CDK [`Function` construct](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.Function.html) with these options set by default: - `runtime`: `provided.al2023` - `code`: the code is automatically zipped from the current directory. - `layers`: the Bref layer is automatically added. - `memorySize`: `1024` - `timeout`: `6` (seconds) The code is automatically zipped from the current directory. You can override this behavior by setting the `code` property: ```typescript import { packagePhpCode } from '@bref.sh/constructs'; new PhpFunction(this, 'MyFunction', { // ... code: packagePhpCode('custom-path', { exclude: ['docs'], }), }); ``` The following paths are always excluded: `.git`, `.idea`, `cdk.out`, `node_modules`, `.bref`, `.serverless`, `tests`. The construct also adds the following options: - `phpVersion` (default: `8.4`): the PHP version to use. #### `ConsoleFunction` This construct deploys a PHP function with the ["console" runtime](https://bref.sh/docs/runtimes/console.html). ```typescript new ConsoleFunction(this, 'Artisan', { handler: 'artisan', }); ``` It inherits from the AWS CDK [`Function` construct](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.Function.html) with these options set by default: - `runtime`: `provided.al2023` - `code`: the code is automatically zipped from the current directory. - `layers`: the Bref layer is automatically added. - `memorySize`: `1024` - `timeout`: `6` (seconds) The code is automatically zipped from the current directory. You can override this behavior by setting the `code` property: ```typescript import { packagePhpCode } from '@bref.sh/constructs'; new ConsoleFunction(this, 'Artisan', { // ... code: packagePhpCode('custom-path', { exclude: ['docs'], }), }); ``` The following paths are always excluded: `.git`, `.idea`, `cdk.out`, `node_modules`, `.bref`, `.serverless`, `tests`. The construct also adds the following options: - `phpVersion` (default: `8.4`): the PHP version to use. --- Source: https://bref.sh/docs/local-development # Local development It is possible to run **web applications** (running with the FPM runtime) locally. > [!TIP] > > To run **event-driven functions** locally, read [Local development for event-driven functions](https://bref.sh/docs/local-development/local-development/event-driven-functions) instead. ## The simple way To keep things simple, you can run your applications locally like you did without Bref. With **Laravel**, run HTTP applications locally using `php artisan serve`, [Laravel Valet](https://laravel.com/docs/valet), or [Laravel Sail](https://laravel.com/docs/sail). You can test CLI commands locally by running them in your terminal using `php artisan my-command`. With **Symfony**, run HTTP applications locally using `symfony server:start` ([documentation](https://symfony.com/doc/current/setup/symfony_server.html)). You can test CLI commands locally by running them in your terminal using `bin/console my-command`. Run your HTTP applications locally via your preferred method to run PHP: Apache, WAMP, or even the built-in PHP server: ```bash php -S localhost:8000 # The application is now available at http://localhost:8000/ ``` ## Docker In order to run the application locally in an environment closer to production, you can run your application using the [Bref Docker images](https://hub.docker.com/u/bref). For example, create the following `docker-compose.yml`: ```yml filename="docker-compose.yml" services: app: image: bref/php-84-dev:3 ports: [ '8000:8000' ] volumes: - .:/var/task environment: HANDLER: public/index.php # Assets will be served from this directory DOCUMENT_ROOT: public ``` You can then run: ```bash docker compose up ``` The application will be available at [http://localhost:8000/](http://localhost:8000/). The `HANDLER` environment variable lets you define which PHP file will be handling all HTTP requests. This should be the same handler that you have defined in `serverless.yml` for your HTTP function. > Currently, the Docker image supports only one PHP handler. If you have multiple HTTP functions in `serverless.yml`, you can duplicate the service in `docker-compose.yml` to have one container per lambda function. ### Read-only filesystem The code will be mounted in `/var/task`, just like in Lambda. But in Lambda, `/var/task` is read-only. When developing locally, it is common to regenerate cache files on the fly (for example Symfony or Laravel cache). You have 2 options: - either mount the whole codebase as writable (per the example above): ```yaml filename="docker-compose.yml" volumes: - .:/var/task ``` - or mount a specific cache directory as writable (better): ```yaml filename="docker-compose.yml" {3} volumes: - .:/var/task:ro - ./storage:/var/task/storage ``` ### Assets If you want to serve assets locally, you can define a `DOCUMENT_ROOT` environment variable: ```yaml {6,7} filename="docker-compose.yml" services: app: # ... environment: HANDLER: public/index.php # Assets will be served from this directory DOCUMENT_ROOT: public ``` In the example above, a `public/assets/style.css` file will be accessible at `http://localhost:8000/assets/style.css`. > [!WARNING] > > Serving assets in production will not work like this out of the box. You will need [to use an S3 bucket](https://bref.sh/docs/local-development/use-cases/websites). ### Console commands You can run console commands in Docker via: ```bash # Laravel (artisan) docker compose run app php artisan ... # Symfony (bin/console) docker compose run app php bin/console ... ``` ### Xdebug The development container (`bref/php--dev`) comes with Xdebug pre-installed. To enable it, create a `php/conf.dev.d/php.ini` file in your project containing: ```ini filename="php/conf.dev.d/php.ini" zend_extension=xdebug.so ``` Now start the debug session by issuing a request to your application [in the browser](https://xdebug.org/docs/remote#starting). #### Xdebug and macOS Docker for Mac uses a virtual machine for running docker. That means you need to use a special host name (`host.docker.internal`) that is mapped to the host machine's IP address. Edit the `php/conf.dev.d/php.ini` file: ```ini filename="php/conf.dev.d/php.ini" {3-6} zend_extension=xdebug.so [xdebug] xdebug.mode = debug xdebug.start_with_request = yes xdebug.client_host = 'host.docker.internal' ``` --- Source: https://bref.sh/docs/local-development/event-driven-functions # Local development for functions It is possible to run **event-driven functions** locally. > [!TIP] > > To run **HTTP applications** (like Laravel or Symfony) locally, read [Local development for HTTP applications](https://bref.sh/docs/local-development) instead. ## With Serverless Framework The `serverless bref:local` command invokes your [PHP functions](https://bref.sh/docs/runtimes/function) locally, using PHP installed on your machine. You can provide an event if your function expects one. > [!NOTE] > > The `serverless bref:local` command is a simpler alternative to the native `serverless invoke local` command, which tries to run PHP using Docker with very little success. Use `serverless bref:local` instead of `serverless invoke local`. For example, given this function: ```php filename="my-function.php" return function (array $event) { return 'Hello ' . ($event['name'] ?? 'world'); }; ``` ```yml filename="serverless.yml" # ... functions: hello: handler: my-function.php runtime: php-84 ``` You can invoke it with or without event data: ```bash $ serverless bref:local -f hello Hello world # With JSON event data $ serverless bref:local -f hello --data '{"name": "Jane"}' Hello Jane # With JSON in a file $ serverless bref:local -f hello --path=event.json Hello Jane ``` > [!WARNING] > > On Windows PowerShell, you must escape the "double quote" char if you write JSON directly in the CLI. Example: > ```bash > $ serverless bref:local -f hello --data '{\"name\": \"Bill\"}' > ``` The `serverless bref:local` command runs using the local PHP installation. If you prefer to use **Docker**, check out the "Without Serverless Framework" section below. ## Without Serverless Framework If you do not use `serverless.yml` but something else, like SAM/AWS CDK/Terraform, use the `vendor/bin/bref-local` command instead: ```bash $ vendor/bin/bref-local # For example $ vendor/bin/bref-local my-function.php Hello world # With JSON event data $ vendor/bin/bref-local my-function.php '{"name": "Jane"}' Hello Jane # With a path to a file containing a JSON event. $ cat event.json { "name": "Alex" } $ vendor/bin/bref-local --path event.json my-function.php Hello Alex ``` ## With Docker If you want to run your function in Docker: ```bash $ docker run --rm -it --entrypoint= -v $(PWD):/var/task:ro bref/php-84:3 vendor/bin/bref-local my-function.php # You can also use the `dev` images for a simpler command (and Xdebug in the image): $ docker run --rm -it -v $(PWD):/var/task:ro bref/php-84-dev:3 vendor/bin/bref-local my-function.php ``` You can also use Docker Compose, like described in [Local development for HTTP applications](https://bref.sh/docs/local-development): ```yml filename="docker-compose.yml" services: app: image: bref/php-84-dev:3 volumes: - .:/var/task ``` Then run functions: ```bash $ docker compose run app vendor/bin/bref-local my-function.php ``` ## API Gateway local development If you build HTTP applications with [API Gateway HTTP events](https://bref.sh/docs/runtimes/fpm-runtime), `serverless bref:local` is a bit impractical because you need to manually craft HTTP events in JSON. Instead, you can use the [`bref/dev-server`](https://github.com/brefphp/dev-server) package to emulate API Gateway locally. --- Source: https://bref.sh/docs/monitoring # Monitoring By default, AWS Lambda publishes PHP logs and metrics to [AWS CloudWatch](https://aws.amazon.com/cloudwatch/). These include HTTP response times, code execution duration, error rates, and more. Here is a summary of recommended tools for monitoring Bref applications: - **Logs and metrics**: CloudWatch (built-in), [Bref Cloud](https://bref.sh/cloud) - **Error tracking**: [Sentry](https://sentry.io) and similar services - **Tracing and performance insights**: [Bref Cloud](https://bref.sh/cloud) with [X-Ray](https://bref.sh/xray) Let's dive into the details of each of them. ## Sentry [Sentry](https://sentry.io) is a popular error tracking service. It works well out of the box with Bref for HTTP applications: install the [Sentry SDK for PHP](https://docs.sentry.io/platforms/php/) (or the [Laravel](https://docs.sentry.io/platforms/php/guides/laravel/) or [Symfony](https://docs.sentry.io/platforms/php/guides/symfony/) integrations) and errors will be tracked automatically. For more advanced use cases, such as tracking Lambda errors outside of PHP-FPM (timeouts, oversized responses…), monitoring event-driven handlers (SQS, EventBridge, S3…), or tracking cold starts and AWS SDK calls, the [Bref Sentry package](https://bref.sh/sentry) extends Sentry's capabilities for AWS Lambda. It is available as a [separate license](https://bref.sh/sentry). ## Bref Cloud [Bref Cloud](https://bref.sh/cloud) monitors serverless PHP applications. It reads logs, metrics and traces from your AWS account. There is no agent to install. ### Overview The overview page shows a diagram of the environment: Lambda functions, API Gateway, CloudFront, SQS queues, S3 buckets and databases. Each component shows live metrics: requests, invocations, errors, queue size, storage. Bref Cloud application overview ### Logs View, search and tail CloudWatch logs. Laravel and Symfony logs are [structured](https://bref.sh/docs/environment/logs): you can filter them by log level or exception class. Log viewer in Bref Cloud ### Metrics The metrics page shows Lambda invocations, duration and errors, as well as API Gateway requests and latency. Queue metrics can also be found in the "Queues" page. You can add graphs to compare metrics over up to 30 days. Metrics in Bref Cloud ### Traces A trace shows what happens inside an invocation: PHP code, database queries, HTTP calls, and AWS SDK calls. For each AWS call, the trace shows the queue, table, or topic it used. X-Ray trace in Bref Cloud The [Bref X-Ray package](https://bref.sh/xray) adds annotations to traces: route, controller, job class, CLI command, cold start. You can add [your own annotations](https://bref.sh/xray/docs#custom-annotations), for example the tenant or the plan of the user. The trace explorer uses annotations as filters. For example, you can list all the traces of one route, of one job, of one tenant, or all the invocations with a cold start. Trace explorer in Bref Cloud Enable [AWS Transaction Search](https://bref.sh/xray/docs#enabling-aws-transaction-search) on your AWS account, and the trace explorer searches up to 30 days of traces instead of 6 hours. It is a one-time switch in the AWS console. ### Performance The Performance page aggregates the traces of the last 30 days. It shows the slowest database queries, the latency of each route, and the slowest jobs. Performance page in Bref Cloud - **Slowest database queries**: SQL queries grouped by statement, with the number of calls, the average duration and the p95 duration. - **Routes**: server-side processing time per route, sorted by number of requests or by latency. Click a route to open its traces. - **Slowest jobs**: queue worker invocations grouped by job class. Click a job to open its traces. The page requires the [Bref X-Ray package](https://bref.sh/xray) in the application and [Transaction Search](https://bref.sh/xray/docs#enabling-aws-transaction-search) on the AWS account. After that, there is nothing else to configure. Bref Cloud computes the statistics when you open the page, caches them for 24 hours, and lets you recompute them at any time. The statistics only include traced invocations (see the [X-Ray sampling rate](https://bref.sh/xray/docs#costs)). ### Failed jobs and health checks Laravel applications have a "Failed jobs" tab next to their queues. It shows the exception of each failed job. You can retry, delete or flush jobs from there. Health checks check that a deployed application works: the database is reachable, the cache works, the Lambda functions use the recommended settings. Laravel only for now. ### X-Ray license Bref Cloud paid plans include a free [Bref X-Ray](https://bref.sh/xray) license. To activate it, contact support via [bref.cloud/support](https://bref.cloud/support) or [Slack](https://bref.sh/slack). [Learn more about Bref Cloud](https://bref.sh/cloud). ## X-Ray [AWS X-Ray](https://aws.amazon.com/xray/) provides distributed tracing for Lambda applications. The [Bref X-Ray package](https://bref.sh/xray) integrates X-Ray with PHP, tracking cold starts, database queries, HTTP calls, AWS SDK calls, and more. It supports both Laravel and Symfony. The package can be used with or without Bref Cloud. Bref Cloud users get a free license (see above), while others can [purchase a standalone license](https://bref.sh/xray). ## Sentry Lambda package As mentioned above, the standard Sentry SDK works great for HTTP applications. The [Bref Sentry package](https://bref.sh/sentry) goes further by adding Lambda-specific capabilities: tracking errors outside of PHP-FPM, monitoring event-driven handlers, and tracking cold starts. The package can be used with or without Bref Cloud. It is available as a [standalone license](https://bref.sh/sentry). ## Bref Dashboard The [Bref Dashboard](https://dashboard.bref.sh/?ref=bref) is an alternative for projects that do not use Bref Cloud. It fetches data from AWS CloudWatch and provides a simple UI for logs and metrics. It requires no setup in AWS and can be used straight away. [![Bref Dashboard](https://bref.sh/docs/monitoring/bref-dashboard.png)](https://dashboard.bref.sh/?ref=bref) ## Tideways [Tideways](https://tideways.com/?ref=bref) is a PHP-specific monitoring and profiling tool that can be used with Bref. It requires setting up a daemon on an EC2 instance in a VPC. [Learn more about using Tideways with Bref](https://bref.sh/docs/monitoring/monitoring/tideways). --- Source: https://bref.sh/docs/use-cases/http // Path relative to the copy in the `website/` folder # Serverless HTTP applications Bref deploys HTTP applications to run on AWS Lambda with [API Gateway](https://aws.amazon.com/api-gateway/): ```mermaid graph LR; START:::mermaidHidden -->|HTTP request| APIGateway(API Gateway):::mermaidAwsColor; APIGateway -->|invoke| Lambda(Lambda):::mermaidAwsColor; ``` On AWS Lambda there is no Apache or Nginx, API Gateway acts as the webserver. Our code is invoked only when there is an HTTP request, and we pay only for the request and the execution time of our code. Bref takes care of setting up everything so that your code runs the same way as on a traditional server with Apache or Nginx. ## Usage HTTP applications are the default use case with Bref. That's why there is (almost) no documentation here. Instead, head to the **Getting started** guide for your framework: } title="Get started with Laravel" arrow="true" href="https://bref.sh/docs/laravel/getting-started" /> } title="Get started with Symfony" arrow="true" href="https://bref.sh/docs/symfony/getting-started" /> ## How it works Bref sets up API Gateway with AWS Lambda and the [PHP-FPM runtime](https://bref.sh/docs/use-cases/runtimes/fpm-runtime). This is done via the `php-xx-fpm` runtime and the `httpApi` event: ```yml filename="serverless.yml" functions: web: handler: public/index.php runtime: php-84-fpm events: - httpApi: '*' ``` This configuration deploys an API Gateway that forwards all routes (`*` is a wildcard) to AWS Lambda. On Lambda, the `php-84-fpm` runtime starts PHP-FPM and forwards all requests to it. PHP-FPM then runs the PHP code. This is perfect for most use-cases: **PHP works like on any server** with PHP-FPM. HTTP routing based on the URL is done by the application/the framework. The `handler` is the entrypoint of the application, usually `public/index.php` in most frameworks. That entrypoint kicks off the framework/your application, which does the routing and invokes the controllers, as usual. All the usual environment variables (like `$_GET`, `$_SERVER`, etc.) and functions (`header()`, etc.) work. That works well with frameworks like Symfony or Laravel that have a single entrypoint (e.g. `public/index.php`). Read the [PHP-FPM runtime documentation](https://bref.sh/docs/use-cases/runtimes/fpm-runtime) to learn more. ### Differences with Apache and Nginx on a server While Bref tries to make the experience as close as possible to running on a traditional server, there are some minor differences. This section lists the differences between running on AWS Lambda and running on a traditional server with Apache or Nginx. #### No `.htaccess` or `nginx.conf` There is no `.htaccess` or `nginx.conf` on AWS Lambda. Instead, you can use `serverless.yml` to configure the routing. #### No `fastcgi_finish_request()` [`fastcgi_finish_request()`](https://www.php.net/manual/en/function.fastcgi-finish-request.php) is disabled by Bref, as it is not possible to run code after the response has been sent to the client. All frameworks are designed to work without it transparently, so you don't need to do anything. #### Differences in URI query parameters In a few edge cases, Bref parses some URI query parameters differently than PHP on a server with PHP-FPM. This is intentional, as this fixes very surprising behaviors from PHP. Here is an exhaustive list of differences: - `?a.b=c` is parsed as: - `['a_b' => 'c']` on a server with PHP-FPM (yes, the dot is replaced by an underscore) - `['a.b' => 'c']` with Bref - `?a=1&a=2&a=3` is parsed as: - `['a' => 3]` on a server with PHP-FPM - `['a' => [1, 2, 3]]` with Bref - `?a=1&a=2&a[]=3&a[]=4` is parsed as: - `['a' => [3, 4]]` on a server with PHP-FPM - `['a' => [1, 2, 3, 4]]` with Bref --- Source: https://bref.sh/docs/use-cases/http/custom-domains # Custom domain names API Gateway generates random domain names for our applications: ``` https://.execute-api..amazonaws.com/ ``` It is possible to replace those URLs by a custom domain. > [!TIP] > > This guide assumes you already own the domain name you will want to use. The first thing to do is register the domain in **ACM** (AWS Certificate Manager) to get an HTTPS certificate. This step is not optional. - Open [this link](https://console.aws.amazon.com/acm/home?region=us-east-1#/wizard/) or manually go in the ACM Console and click "Request a new certificate" in the `us-east-1` region (the region used for global "edge" certificates) - Add your domain name and click "Next" - Choose the domain validation of your choice. - domain validation will require you to add CNAME entries to your DNS configuration - email validation will require you to click a link you will receive in an email sent to `admin@your-domain.com` After validating the domain and the certificate we can now link the custom domain to our application via API Gateway. - Open [API Gateway's "Custom Domain" configuration](https://console.aws.amazon.com/apigateway/main/publish/domain-names) - **Switch to the region of your application** - Click "Create" - Enter your domain name, select the certificate you created above and save - Edit the domain that was created - Click "Configure API mappings" to add an "API mapping": select your application and the `$default` stage (or `dev` in some cases), for example: ![](https://bref.sh/docs/use-cases/http/custom-domains-path-mapping.png) - After saving the "API mappings", find the `API Gateway domain name` in the "Configurations" tab - Create a CNAME entry in your DNS to point your domain name to this domain After waiting for the DNS change to propagate (sometimes up to several hours) your website is now accessible via your custom domain. > [!TIP] > > You can also take a look at the plugin [serverless-domain-manager](https://www.serverless.com/plugins/serverless-domain-manager). > It handles the custom domain creation and optionally adds the Route53 record if asked. It is still necessary to create the ACM certificate manually. > > A basic implementation is proposed here : https://www.serverless.com/blog/serverless-api-gateway-domain#create-a-custom-domain-in-api-gateway --- Source: https://bref.sh/docs/use-cases/http/binary-requests-responses # Binary requests and responses AWS Lambda is only used for executing code. Serving assets via PHP does not make sense as this would be a waste of resources and money. > [!TIP] > > Deploying a website with assets (e.g. CSS, JavaScript, images) is covered in [the "Websites" documentation](https://bref.sh/docs/use-cases/websites). In some cases however, you want to serve images (or other assets) via PHP. One example would be if you served generated images or PDFs via PHP. By default, API Gateway **does not support binary HTTP requests or responses** like images, PDF, binary files… To achieve this, you need to enable the option for binary media types in `serverless.yml` as well as define the `BREF_BINARY_RESPONSES` environment variable: ```yml filename="serverless.yml" provider: # ... apiGateway: binaryMediaTypes: - '*/*' environment: BREF_BINARY_RESPONSES: '1' ``` This will make API Gateway support binary file uploads and downloads, and Bref will automatically encode responses to base64 (which is what API Gateway expects for binary responses). Be aware that the max upload and download size is **6MB** and because [base64 encoding adds a ~33% overhead](https://en.wikipedia.org/wiki/Base64) to the response, downloads are limited to ~4.5MB in size. For larger files, use AWS S3. An example is available in [Serverless Visually Explained](https://serverless-visually-explained.com/). --- Source: https://bref.sh/docs/use-cases/http/advanced-use-cases # Advanced HTTP use-cases > [!TIP] > > If you are getting started with Bref, read the guides for **Laravel** ([Get started](https://bref.sh/docs/laravel/getting-started)), **Symfony** ([Get started](https://bref.sh/docs/symfony/getting-started)), or other PHP frameworks ([Get started](https://bref.sh/docs/default/getting-started)). > > This documentation is for advanced use-cases. ## Alternative AWS architectures By default, Bref uses API Gateway v2 HTTP APIs to run HTTP applications. However, there are other ways to run HTTP applications on AWS Lambda: using [API Gateway](https://aws.amazon.com/api-gateway/), [Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html), or [AWS Application Load Balancer (ALB)](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html): - API Gateway v1 "REST" + Lambda - API Gateway v2 "HTTP" + Lambda - Lambda Function URL - AWS ALB (Application Load Balancer) + Lambda Bref supports all 4, but API Gateway v2 "HTTP" is the default because it is the simplest option that supports both HTTP APIs and websites with custom domains. > [!TIP] > > If you are getting started, **stay with the defaults**. The documentation and integrations will be much simpler to follow. > > You can change the architecture later if you need to. Here is a summary of the differences between the 4 options: | | API Gateway v1 REST | API Gateway v2 HTTP | Function URL | ALB | |---------------------------------|:---------------------:|:------------------------------------:|:------------------------------------:|:----------------:| | Pricing (Lambda cost excluded) | $3.5/million requests | $1/million requests | Free (no extra costs) | Starts at $22/mo | | Custom domain | ✅ | ✅ | No, but possible with CloudFront | ✅ | | HTTP/HTTPS Support | ✅ | HTTPS only (use CloudFront for HTTP) | HTTPS only (use CloudFront for HTTP) | ✅ | | Authorizers | IAM, Lambda, Cognito | IAM, Lambda, Cognito, OAuth 2 | IAM | OIDC, Cognito | | CORS | ✅ | ✅ | ✅ | ❌ | | CloudWatch metrics | ✅ | ✅ | ✅ | ✅ | | CloudWatch access logs | ✅ | ✅ | ❌ | ✅ | | Caching | ✅ | ❌ | ❌ | ❌ | | API key management | ✅ | ❌ | ❌ | ❌ | | Request transformation | ✅ | ❌ | ❌ | ❌ | | Request/response validation | ✅ | ❌ | ❌ | ❌ | | Maximum request/response size | 6MB | 6MB | 6MB | 1MB | | Maximum HTTP response timeout | 29s | 30s | 15 minutes | 15 minutes | | Added latency to HTTP responses | 25ms | 15ms | 10ms | | You can read more details [in the "Choosing between REST APIs and HTTP APIs" AWS documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html). ### API Gateway v2 HTTP API The simplest way to set up API Gateway is to have all incoming requests sent to our application in one Lambda function: ```yml filename="serverless.yml" functions: hello: handler: index.php # ... events: - httpApi: '*' ``` That works well with frameworks like Symfony or Laravel that have a single entrypoint (e.g. `public/index.php`) combined with the [PHP-FPM runtime](https://bref.sh/docs/runtimes/fpm-runtime). You can look at more advanced API Gateway routing options in the [serverless documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/http-api.md). ### Lambda Function URLs Since 2022, AWS Lambda can respond to HTTP requests via [Lambda Function URLs](https://aws.amazon.com/blogs/aws/announcing-aws-lambda-function-urls-built-in-https-endpoints-for-single-function-microservices/). This is a new way to invoke Lambda functions via HTTP without using API Gateway. You can deploy a Lambda Function URL [via the following configuration](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/functions.md#lambda-function-urls): ```yml filename="serverless.yml" functions: hello: handler: index.php # ... url: true ``` ### API Gateway v1 REST API The syntax is slightly different from API Gateway v2 HTTP APIs as we must use a different `events` configuration. Here is an example that sends all requests to a single Lambda function: ```yml filename="serverless.yml" functions: hello: handler: index.php # ... events: - http: 'ANY /' - http: 'ANY /{proxy+}' ``` Learn more [in the Serverless Framework documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/apigateway.md). ### Application Load Balancer Application Load Balancer (ALB) is a managed load balancer that can be used to route HTTP requests to Lambda functions. It is a more advanced option that is interesting at high scale as ALB can be much cheaper than API Gateway. ```yml filename="serverless.yml" functions: hello: handler: index.php # ... events: - alb: listenerArn: arn:aws:elasticloadbalancing:us-east-1:12345:listener/app/my-load-balancer/50dc6c495c0c9188/ priority: 1 conditions: path: '/*' ``` Learn more [in the Serverless Framework documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/alb.md). ## PHP handlers > [!TIP] > > This section applies to all 4 approaches: API Gateway v1, v2, ALB, and Function URLs. > > Bref abstracts the differences so that the same code can be used with all 4 solutions. There are two ways to handle HTTP events with PHP: - via the [PHP-FPM runtime](https://bref.sh/docs/runtimes/fpm-runtime) (simplest, this is Bref's default) - via the [Event-Driven Function runtime](https://bref.sh/docs/runtimes/function) (more advanced) Here is a full comparison between both approaches: | | PHP-FPM runtime | Event-Driven Function handler | |----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| | What are the use cases? | To build websites, APIs, etc. This should be the **default approach** as it's compatible with mature PHP frameworks and tools. | Build event-driven microservices, or run Laravel Octane, or Symfony with a keep-alive process (like Roadrunner/Swoole). | | Why does that solution exist? | For out-of-the-box compatibility with existing applications and frameworks. | To match how other languages run in AWS Lambda, i.e. to build very specialized HTTP endpoints. | | How it runs under the hood | Using PHP-FPM. | Using the PHP CLI. | | What does the routing (i.e. separate pages)? | Your PHP framework (one Lambda receives all the URLs). | API Gateway: we define one Lambda and one handler class per route. | | How to read the request? | `$_GET`, `$_POST`, etc. | The `$request` parameter (PSR-7 request). | | How to write a response? | `echo`, `header()` function, etc. | Returning a PSR-7 response from the handler class. | | How does it work? | Bref turns an API Gateway event into a FastCGI (PHP-FPM) request. | Bref turns an API Gateway event into a PSR-7 request. | | Is each request handled in a separate PHP process? | Yes (that's how PHP-FPM works). | Yes by default (Bref replicates that to avoid surprises) but can be disabled for optimal performance. | ### With the PHP-FPM runtime This is perfect for most use-cases: **PHP works like on any server** with PHP-FPM. HTTP routing based on the URL is done by the application/the framework. This approach is already covered by most of the Bref documentation, so we won't go into details here. You can read more about [the PHP-FPM runtime here](https://bref.sh/docs/runtimes/fpm-runtime). ### With the Event-Driven Function runtime This is more advanced, as PHP does not run in a traditional PHP-FPM environment. It can be used with or without a PHP framework. When used with a framework, understand that the whole HTTP stack (like HTTP middlewares) of the framework does not run. You are responsible for invoking the PHP code that should run. > [!TIP] > > Note: this approach is used to run Laravel Octane or Symfony with a keep-alive process (like Roadrunner/Swoole). These use cases are not detailed here, read [about Laravel Octane](https://bref.sh/docs/laravel/octane) or [about Symfony "Keep-Alive"](https://bref.sh/docs/symfony/keep-alive) instead. The `handler` must be a PHP function, or a PSR-15 implementation. Indeed, Bref natively supports the [PSR-15](https://www.php-fig.org/psr/psr-15/#2-interfaces) and [PSR-7](https://www.php-fig.org/psr/psr-7/) standards. Here is an example: ```php getQueryParams()['name'] ?? 'world'; return new Response(200, [], "Hello $name"); } } ``` Then, create a Lambda function that listens to HTTP events with the handler you created: ```yml filename="serverless.yml" functions: # ... hello: handler: App\MyHttpHandler runtime: php-84 # Lambda Function URL url: true # Or API Gateway events: # API Gateway v2 - httpApi: 'GET /hello' # API Gateway v1 - http: 'GET hello' ``` The `App\MyHttpHandler` class will be instantiated by Laravel's service container. ```yml filename="serverless.yml" functions: # ... hello: handler: App\MyHttpHandler runtime: php-84 # Lambda Function URL url: true # Or API Gateway events: # API Gateway v2 - httpApi: 'GET /hello' # API Gateway v1 - http: 'GET hello' ``` The `App\MyHttpHandler` class will be instantiated by Symfony's service container. ```yml filename="serverless.yml" functions: # ... hello: handler: handler.php runtime: php-84 # Lambda Function URL url: true # Or API Gateway events: # API Gateway v2 - httpApi: 'GET /hello' # API Gateway v1 - http: 'GET hello' ``` The file `handler.php` should return the handler instance: ```php filename="handler.php" Since a handler is a controller for a specific route, we can use the API Gateway routing to deploy multiple functions: ```yml filename="serverless.yml" functions: create-article: handler: App\CreateArticleController runtime: php-84 events: - httpApi: 'POST /articles' get-article: handler: App\GetArticleController runtime: php-84 events: - httpApi: 'GET /articles/{id}' ``` Path parameters (e.g. `{id}` in the example above) are available as request attributes in the PSR-7 request: ```php $id = $request->getAttribute('id'); ``` [Full reference of HTTP events in `serverless.yml`](https://github.com/oss-serverless/osls/blob/4.x/docs/events/http-api.md). #### Lambda event and context The API Gateway event and Lambda context are available as attributes on the PSR-7 request: ```php /** @var $event Bref\Event\Http\HttpRequestEvent */ $event = $request->getAttribute('lambda-event'); /** @var $context Bref\Context\Context */ $context = $request->getAttribute('lambda-context'); ``` If you're looking for the request context array, for example when using a [Lambda authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response): ```php $requestContext = $request->getAttribute('lambda-event')->getRequestContext(); ``` ## Cold starts On applications with regular traffic, cold starts only represent about **0.2% of requests**. For low-traffic applications, you can pre-warm your HTTP function to avoid them. Read the full [Cold starts documentation](https://bref.sh/docs/environment/cold-starts) for details and the warming configuration. --- Source: https://bref.sh/docs/use-cases/websites # Serverless PHP websites In this guide, you will learn how to set up assets for your serverless PHP website. > [!TIP] > > This guide assumes that you have already gotten started with Bref. If you haven't, [get started first](https://bref.sh/docs/setup). ## Architecture Websites usually contain 2 parts: - PHP code, running on AWS Lambda + API Gateway (read the [HTTP applications](https://bref.sh/docs/use-cases/http) guide) - static assets (CSS, JS…), [hosted on AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html) To combine both, we can use [AWS CloudFront](https://aws.amazon.com/cloudfront/). CloudFront acts both as a CDN and as reverse proxy to route requests to PHP or assets on S3. ![](https://bref.sh/docs/use-cases/websites/cloudfront.svg) This lets us host everything under the same domain and support both HTTP and HTTPS. > [!TIP] > > If you don't want to use CloudFront, you can read the [older version of this documentation](https://github.com/brefphp/bref/blob/d1dd690d020cd03f134010db456bb61a6d0ffafb/docs/websites.md#architectures) which featured running PHP and the assets on two different domains. ## Setup While it is possible to set up CloudFront manually, the easiest approach is to use the [Server-side website construct of the Lift plugin](https://github.com/getlift/lift/blob/master/docs/server-side-website.md). First install the plugin: ```bash serverless plugin install -n serverless-lift ``` Then add this configuration to `serverless.yml`: ```yml filename="serverless.yml" {5,7-16} # ... plugins: - ./vendor/bref/bref - serverless-lift constructs: website: type: server-side-website versionedAssets: true assets: '/build/*': public/build '/vendor/*': public/vendor '/favicon.ico': public/favicon.ico '/robots.txt': public/robots.txt # add here any file or directory that needs to be served from S3 ``` We enable [`versionedAssets`](https://github.com/getlift/lift/blob/master/docs/server-side-website.md#versioned-assets) so that deployments are zero-downtime: Laravel + Vite generate asset file names with a unique hash, and Lift uploads the new hashed assets to S3 *before* the new PHP code is deployed. This guarantees that the assets referenced by the freshly-deployed code are already available. Before deploying, compile your assets: ```bash npm run build ``` Then add this configuration to `serverless.yml`: ```yml filename="serverless.yml" {5,7-16} # ... plugins: - ./vendor/bref/bref - serverless-lift constructs: website: type: server-side-website versionedAssets: true assets: '/bundles/*': public/bundles '/build/*': public/build '/favicon.ico': public/favicon.ico '/robots.txt': public/robots.txt # add here any file or directory that needs to be served from S3 ``` We enable [`versionedAssets`](https://github.com/getlift/lift/blob/master/docs/server-side-website.md#versioned-assets) so that deployments are zero-downtime: Webpack Encore generates asset file names with a unique hash in production (`.enableVersioning()` is enabled by default in the Symfony recipe), and Lift uploads the new hashed assets to S3 *before* the new PHP code is deployed. This guarantees that the assets referenced by the freshly-deployed code are already available. Non-hashed files (like `/bundles/*`) are not affected: they are updated after the deployment, like by default. Because this construct sets the `X-Forwarded-Host` header by default, you should add it in your `trusted_headers` config, otherwise Symfony might generate wrong URLs. ```yml filename="config/packages/framework.yaml" /, 'x-forwarded-host'/ trusted_headers: [ 'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-host' ] ``` Before deploying, compile your assets: ```bash php bin/console assets:install --env prod # if using Webpack Encore, additionally run yarn encore production ``` Then add this configuration to `serverless.yml`: ```yml filename="serverless.yml" {5,7-15} # ... plugins: - ./vendor/bref/bref - serverless-lift constructs: website: type: server-side-website assets: '/js/*': public/js '/css/*': public/css '/favicon.ico': public/favicon.ico '/robots.txt': public/robots.txt # add here any file or directory that needs to be served from S3 ``` If you need to compile your assets, make sure to run the command before deploying. Now deploy everything: ```bash serverless deploy ``` Lift will create all the required resources and take care of uploading your assets to S3 automatically. You can access your website using the URL that Lift outputs at the end of the deployment. > [!TIP] > > The first deployment takes 5 minutes because CloudFront is a distributed service. The next deployments that do not modify CloudFront's configuration will not suffer from this delay. ## Assets in templates Assets referenced in Blade templates should be via the `asset()` helper: ```blade ``` If your templates reference some assets via direct path, you should edit them to use the `asset()` helper: ```diff - + ``` For the above configuration to work, assets must be referenced in Twig templates via the `asset()` helper as [recommended by Symfony](https://symfony.com/doc/current/templates.html#linking-to-css-javascript-and-image-assets): ```diff - + ``` If your `serverless.yml` configuration has different CloudFront routes for assets than the directory layout in your codebase, you may need to update your templates to use the correct paths. ## Custom domain name > [!NOTE] > > When using CloudFront, the custom domain must be set up on CloudFront, not API Gateway. If you have already set up your domain on API Gateway you will need to remove it before continuing. The first thing to do is register the domain in **ACM** (AWS Certificate Manager) to get an HTTPS certificate. This step is not optional. - Open [this link](https://console.aws.amazon.com/acm/home?region=us-east-1#/wizard/) or manually go in the ACM Console and click "Request a new certificate" **in the `us-east-1` region** (CloudFront requires certificates from `us-east-1` because it is a global service) - Add your domain name and click "Next". - Choose the domain validation of your choice: - domain validation will require you to create DNS entries (this is **recommended** because it renews the certificate automatically) - email validation will require you to click a link you will receive in an email sent to `admin@your-domain.com` Copy the ARN of the ACM certificate. It should look like this: ``` arn:aws:acm:us-east-1:216536346254:certificate/322f12ee-1165-4bfa-a41f-08c932a2935d ``` Next, add your domain name and certificate in `serverless.yml`: ```yml filename="serverless.yml" # ... constructs: website: # ... domain: mywebsite.com certificate: ``` The last step will be to point your domain name DNS records to the CloudFront domain: - copy the domain outputted by Lift during `serverless deploy` (or run `serverless info` to retrieve it) - create a CNAME to point your domain name to this URL - if you use Route53 you can read [the official guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-cloudfront-distribution.html) - if you use another registrar and you want to point your root domain (without `www.`) to CloudFront, you will need to use a registrar that supports this (for example [Cloudflare allows this with a technique called CNAME flattening](https://developers.cloudflare.com/dns/cname-flattening/)) Lift supports more advanced use cases like multiple domains, root domain to `www` redirects, and more. Check out [the Lift documentation](https://github.com/getlift/lift/blob/master/docs/server-side-website.md). ## Compressing HTTP responses By default, Lift enables gzip and brotli compression for static assets served from S3. That means that if a client supports compressed responses (via the `Accept-Encoding` header), [CloudFront will cache and serve a compressed version of the asset](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html#compressed-content-cloudfront-how-it-works), which is usually smaller and faster to transfer. However, dynamic responses generated by PHP (for example HTML pages) are not compressed. The reason is that CloudFront's cache is disabled for requests to PHP (Lift sets up the [`CachingDisabled` policy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html#managed-cache-policy-caching-disabled)), and therefore CloudFront does not compress non-cached responses. You can compress PHP responses by using an HTTP middleware that compresses the response body and sets the `Content-Encoding` header. Here is an example for Laravel: ```php filename="app/Http/Middleware/CompressResponse.php" class CompressResponse { public function handle(Request $request, Closure $next) { /** @var \Illuminate\Http\JsonResponse $response */ $response = $next($request); if (! in_array('gzip', $request->getEncodings())) { return $response; } if ($response->headers->has('Content-Encoding')) { return $response; } $content = $response->getContent(); if (! $content) { return $response; } $compressed = gzencode($content, 9); $response->setContent($compressed); $response->headers->set('Content-Encoding', 'gzip'); $response->headers->set('Content-Length', (string) strlen($compressed)); return $response; } } ``` --- Source: https://bref.sh/docs/use-cases/static-websites # Static websites To serve a static website, we do not need PHP nor AWS Lambda. We can host the website on any static hosting service (like Netlify, Vercel, etc.) or on AWS with CloudFront and S3: ```mermaid graph LR; START:::mermaidHidden -->|HTTP request| CloudFront(CloudFront):::mermaidAwsColor; CloudFront -->|HTTP request| S3(S3):::mermaidAwsColor; ``` To deploy such a website with Bref and `serverless.yml`, we can use [Lift](https://github.com/getlift/lift): - The [`static-website` construct](https://github.com/getlift/lift/blob/master/docs/static-website.md) for plain static HTML websites - The [`single-page-app` construct](https://github.com/getlift/lift/blob/master/docs/single-page-app.md) for Single-Page Applications like React or VueJS Lift allows configuring custom domains, root domain to www redirects, and more. Check out the documentation linked above for more details. --- Source: https://bref.sh/docs/use-cases/cron # Cron tasks on AWS Lambda A Lambda function can be invoked on a schedule using the `schedule` event. This is useful for running cron tasks, such as sending emails or cleaning up data. For example: ```yml filename="serverless.yml" {5-8} functions: cron: # ... events: # the schedule can be defined as a rate - schedule: rate(1 hour) # or as a cron expression - schedule: cron(0 12 * * ? *) ``` ## CLI commands Cron events can be used to run CLI commands with the [Console runtime](https://bref.sh/docs/runtimes/console). In that case, use the `php-xx-console` runtime (for example `php-84-console`). This is usually best when coupled with a framework like Laravel or Symfony, or when porting an existing cron task to AWS Lambda. ```yml filename="serverless.yml" functions: # ... cron: handler: artisan runtime: php-84-console events: - schedule: rate: rate(1 hour) # The command needs to be passed as a JSON string # (that is why it's quoted twice: '"..."') input: '"my-command --option=value"' ``` The configuration above will run `php artisan my-command --option=value` every hour in the Lambda function named "cron". Note that Laravel already provides a [scheduler](https://laravel.com/docs/scheduling) that can be used instead of the `schedule` event. If you want to use it instead, run the `artisan schedule:run` command every minute: ```yml filename="serverless.yml" functions: # ... artisan: handler: artisan runtime: php-84-console events: - schedule: rate: rate(1 minute) input: '"schedule:run"' ``` ```yml filename="serverless.yml" functions: # ... cron: handler: bin/console runtime: php-84-console events: - schedule: rate: rate(1 hour) # The command needs to be passed as a JSON string # (that is why it's quoted twice: '"..."') input: '"my-command --option=value"' ``` The configuration above will run `bin/console my-command --option=value` every hour in the Lambda function named "cron". ```yml filename="serverless.yml" functions: # ... cron: handler: my-script.php runtime: php-84-console events: - schedule: rate: rate(1 hour) ``` The configuration above will run `php my-script.php` every hour in the Lambda function named "cron". If you need to pass CLI options to the script, use the `input` option: ```yml filename="serverless.yml" {9-11} functions: # ... cron: handler: my-script.php runtime: php-84-console events: - schedule: rate: rate(1 hour) # The command needs to be passed as a JSON string # (that is why it's quoted twice: '"..."') input: '"my-command --option=value"' ``` The configuration above will run `php my-script.php my-command --option=value` every hour in the Lambda function named "cron". Read more about the options for the `schedule` event in the [Serverless documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/schedule.md). ## Cron functions On top of running CLI cron tasks with the `php-xx-console` runtime, we can also run **event-driven functions** (using the [PHP function runtime](https://bref.sh/docs/runtimes/function)) as cron tasks. ```yml filename="serverless.yml" functions: # ... cron: handler: App\MyCronHandler runtime: php-84 events: - schedule: rate: rate(1 hour) ``` The handler can be a class implementing the `Handler` interface: ```php namespace App; use Bref\Context\Context; class MyCronHandler implements \Bref\Event\Handler { public function handle($event, Context $context): void { echo 'Hello ' . ($event['name'] ?? 'world'); } } ``` The configuration above will run `MyCronHandler::handle()` every hour. It is possible to provide data inside the `$event` variable via the `input` option: ```yml filename="serverless.yml" {6-8} functions: cron: events: - schedule: rate: rate(1 hour) input: foo: bar hello: world ``` ```yml filename="serverless.yml" functions: # ... cron: handler: App\MyCronHandler runtime: php-84 events: - schedule: rate: rate(1 hour) ``` The handler can be a class implementing the `Handler` interface: ```php namespace App; use Bref\Context\Context; class MyCronHandler implements \Bref\Event\Handler { public function handle($event, Context $context): void { echo 'Hello ' . ($event['name'] ?? 'world'); } } ``` The configuration above will run `MyCronHandler::handle()` every hour. It is possible to provide data inside the `$event` variable via the `input` option: ```yml filename="serverless.yml" {6-8} functions: cron: events: - schedule: rate: rate(1 hour) input: foo: bar hello: world ``` ```yml filename="serverless.yml" functions: # ... cron: handler: function.php runtime: php-84 events: - schedule: rate: rate(1 hour) ``` The example above will run the function returned by `function.php` every hour in AWS Lambda. For example: ```php Read more about the options for the `schedule` event in the [Serverless documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/schedule.md). --- Source: https://bref.sh/docs/use-cases/s3 # S3 file processing [S3 can trigger Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/with-s3.html) whenever a file is added, modified, or deleted in an S3 bucket. ```mermaid graph LR; START:::mermaidHidden -->|file| S3(S3):::mermaidAwsColor; S3 --> Lambda(Lambda):::mermaidAwsColor; ``` This can be used to process files, for example to resize images, generate thumbnails, convert videos, after they are uploaded to S3. To handle S3 events, extend the `S3Handler` class: ```php use Bref\Context\Context; use Bref\Event\S3\S3Event; use Bref\Event\S3\S3Handler; class MyHandler extends S3Handler { public function handleS3(S3Event $event, Context $context): void { $bucketName = $event->getRecords()[0]->getBucket()->getName(); $fileName = $event->getRecords()[0]->getObject()->getKey(); // do something with the file } } ``` Then, create a Lambda function that listens to S3 events with the handler you created: ```yml filename="serverless.yml" functions: # ... resizeImage: handler: App\MyHandler runtime: php-84 events: - s3: photos ``` The `App\MyHandler` class will be instantiated by Laravel's service container. ```yml filename="serverless.yml" functions: # ... resizeImage: handler: App\MyHandler runtime: php-84 events: - s3: photos ``` The `App\MyHandler` class will be instantiated by Symfony's service container. ```yml filename="serverless.yml" functions: # ... resizeImage: handler: handler.php runtime: php-84 events: - s3: photos ``` The file `handler.php` should return the handler instance: ```php filename="handler.php" The S3 bucket will automatically be created on deployment. You can listen to an existing S3 bucket via [the `existing: true` option](https://github.com/oss-serverless/osls/blob/4.x/docs/events/s3.md#using-existing-buckets). Or you can use the [`Storage` feature of the Lift plugin](https://github.com/getlift/lift/blob/master/docs/storage.md). For example: ```yml filename="serverless.yml" constructs: reports-bucket: type: storage functions: resizeImage: handler: handler.php runtime: php-84 events: - s3: bucket: ${construct:reports-bucket.bucketName} existing: true event: s3:ObjectCreated:* # ... ``` Learn more about all the options available for S3 in `serverless.yml` [in the Serverless Framework documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/s3.md). > [!CAUTION] > > Watch out for recursive triggers: if your Lambda function writes files to the same S3 bucket, it will trigger itself again. You can avoid this by using a different bucket for the output files, or by using a prefix for the output files. --- Source: https://bref.sh/docs/use-cases/sqs # SQS asynchronous tasks SQS is a service (like RabbitMQ) that allows you to queue messages (aka "jobs"). It is a good fit for asynchronous tasks because it integrates natively with AWS Lambda. ```mermaid graph LR; START:::mermaidHidden -->|job| SQS(SQS):::mermaidAwsColor; SQS -->|job| Lambda(Lambda):::mermaidAwsColor; ``` Whenever a new message (job) is sent to SQS, Lambda is invoked with the message data. That means that there is no need to poll SQS, or run daemon/long-running processes to wait for messages. Lambda is invoked only when there are messages to process. > [!TIP] > > If you are using Laravel or Symfony, you should look at the Laravel Queues integration or Symfony Messenger integration instead of integrating with SQS events directly: > > - [Laravel Queues integration](https://bref.sh/docs/laravel/queues) > - [Symfony Messenger integration](https://bref.sh/docs/symfony/messenger) ## Handling SQS events To handle [SQS events](https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html), extend the `SqsHandler` class: ```php use Bref\Context\Context; use Bref\Event\Sqs\SqsEvent; use Bref\Event\Sqs\SqsHandler; class MyHandler extends SqsHandler { public function handleSqs(SqsEvent $event, Context $context): void { foreach ($event->getRecords() as $record) { // We can retrieve the message body of each record via `->getBody()` $body = $record->getBody(); // do something } } } ``` Then, create a Lambda function that listens to SQS events with the handler you created: ```yml filename="serverless.yml" functions: # ... resizeImage: handler: App\MyHandler events: - sqs: arn: arn:aws:sqs:eu-west-1:111111111111:queue-name # process one message at a time batchSize: 1 ``` The `App\MyHandler` class will be instantiated by Laravel's service container. ```yml filename="serverless.yml" functions: # ... resizeImage: handler: App\MyHandler events: - sqs: arn: arn:aws:sqs:eu-west-1:111111111111:queue-name # process one message at a time batchSize: 1 ``` The `App\MyHandler` class will be instantiated by Symfony's service container. ```yml filename="serverless.yml" functions: # ... resizeImage: handler: handler.php events: - sqs: arn: arn:aws:sqs:eu-west-1:111111111111:queue-name # process one message at a time batchSize: 1 ``` The file `handler.php` should return the handler instance: ```php filename="handler.php" ## Creating SQS queues It is possible to deploy a preconfigured SQS queue in `serverless.yml` using the [`Queue` feature of the Lift plugin](https://github.com/getlift/lift/blob/master/docs/queue.md). For example: ```yml filename="serverless.yml" constructs: my-queue: type: queue worker: handler: handler.php ``` ## Partial Batch Response While handling a batch of records, you can mark it as partially successful to reprocess only the failed records. In your function declaration in `serverless.yml`, set `functionResponseType` to `ReportBatchItemFailures` to let your function return a partial success result if one or more messages in the batch have failed. ```yml filename="serverless.yml" functions: worker: handler: handler.php events: - sqs: arn: arn:aws:sqs:eu-west-1:111111111111:queue-name batchSize: 10 functionResponseType: ReportBatchItemFailures ``` In your PHP code, you can now use the `markAsFailed` method: ```php public function handleSqs(SqsEvent $event, Context $context): void { foreach ($event->getRecords() as $record) { // do something // if something went wrong, mark the record as failed $this->markAsFailed($record); } } ``` ## SQS polling and AWS costs AWS Lambda maintains a continuous "poller" for SQS that invokes our function when messages are available. This process, while eliminating the need for manual polling, still creates SQS requests, which can incur charges. A single SQS queue with Lambda will be free since it fits in the AWS free tier. However, if you plan on deploying a lot of SQS queues, you should be aware of potential costs (about $0.26/month per queue). If you want to reduce the number of SQS requests (and thus potential costs), you can use [the `MaximumBatchingWindowInSeconds` option](https://github.com/getlift/lift/blob/master/docs/queue.md#maximum-batching-window). This setting delays message delivery to the Lambda function when batching, aiming to assemble the largest possible batch within the specified timeframe, counted as a single SQS API request. Note that this technique introduces a delay in message processing, so be aware of the trade-off. ## Learn more Learn more about all the options available for SQS events in `serverless.yml` [in the Serverless Framework documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/sqs.md). You can also learn more about SQS, workers, scaling queues, and dealing with errors in [Serverless Visually Explained](https://serverless-visually-explained.com/). --- Source: https://bref.sh/docs/use-cases/eventbridge # EventBridge event bus [EventBridge](https://aws.amazon.com/eventbridge/) is a managed event bus that is perfect for exchanging asynchronous messages between applications and microservices. ```mermaid graph LR; EventBridge(EventBridge):::mermaidAwsColor; Lambda1(Lambda A):::mermaidAwsColor -->|message| EventBridge; EventBridge -->|message| Lambda2(Lambda B):::mermaidAwsColor; EventBridge -->|message| Lambda3(Lambda C):::mermaidAwsColor; ``` To handle EventBridge events, extend the `EventBridgeHandler` class: ```php use Bref\Context\Context; use Bref\Event\EventBridge\EventBridgeEvent; use Bref\Event\EventBridge\EventBridgeHandler; class MyHandler extends EventBridgeHandler { public function handleEventBridge(EventBridgeEvent $event, Context $context): void { // We can retrieve the message data via `$event->getDetail()` $message = $event->getDetail(); // do something } } ``` Then, create a Lambda function that listens to EventBridge events with the handler you created: ```yml filename="serverless.yml" functions: # ... resizeImage: handler: App\MyHandler runtime: php-84 events: - eventBridge: pattern: detail-type: - 'MyCustomEvent' ``` The `App\MyHandler` class will be instantiated by Laravel's service container. ```yml filename="serverless.yml" functions: # ... resizeImage: handler: App\MyHandler runtime: php-84 events: - eventBridge: pattern: detail-type: - 'MyCustomEvent' ``` The `App\MyHandler` class will be instantiated by Symfony's service container. ```yml filename="serverless.yml" functions: # ... resizeImage: handler: handler.php runtime: php-84 events: - eventBridge: pattern: detail-type: - 'MyCustomEvent' ``` The file `handler.php` should return the handler instance: ```php filename="handler.php" You can learn more about messaging with EventBridge in [Serverless Visually Explained](https://serverless-visually-explained.com/). [![](https://bref.sh/docs/use-cases/eventbridge.png)](https://serverless-visually-explained.com/) Learn more about all the options available for EventBridge in `serverless.yml` [in the Serverless Framework documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/event-bridge.md). --- Source: https://bref.sh/docs/use-cases/websockets # WebSockets WebSockets are great for bringing real-time updates to a web application. They allow sending events from the backend application to the frontend (JavaScript) application. Implementing WebSockets implies maintaining a long-lived connection between the JavaScript client and the backend. As you can imagine, that is not possible with AWS Lambda. Indeed, Lambda only runs code on events: it is impossible to run code continuously. API Gateway [can solve that problem](https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop.html): API Gateway maintains the long-lived WebSocket connections and invokes Lambda when an event happens (connection, disconnection, message). ```mermaid graph LR; API(API Gateway):::mermaidAwsColor; B1(Browser):::mermaidBlack -->|WebSocket connection| API; B2(Browser):::mermaidBlack -->|WebSocket connection| API; B3(Browser):::mermaidBlack -->|WebSocket connection| API; API --> Lambda(Lambda):::mermaidAwsColor; ``` To handle WebSocket events, extend the `WebsocketHandler` class: ```php use Bref\Context\Context; use Bref\Event\ApiGateway\WebsocketEvent; use Bref\Event\ApiGateway\WebsocketHandler; use Bref\Event\Http\HttpResponse; class MyHandler extends WebsocketHandler { public function handleWebsocket(WebsocketEvent $event, Context $context): HttpResponse { $route = $event->getRouteKey(); $eventType = $event->getEventType(); $body = $event->getBody(); return new HttpResponse('ok'); } } ``` To send a message to a connected client, you can use the [bref/api-gateway-websocket-client library](https://github.com/brefphp/api-gateway-websocket-client) to make an HTTP request to the endpoint provided by AWS. Learn more about using WebSockets in `serverless.yml` [in the Serverless Framework documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/websocket.md). > [!TIP] > > A complete WebSocket example is available in [Serverless Visually Explained](https://serverless-visually-explained.com/). --- Source: https://bref.sh/docs/use-cases/sns # SNS events To handle SNS events, extend the `SnsHandler` class: ```php use Bref\Context\Context; use Bref\Event\Sns\SnsEvent; use Bref\Event\Sns\SnsHandler; class MyHandler extends SnsHandler { public function handleSns(SnsEvent $event, Context $context): void { foreach ($event->getRecords() as $record) { $message = $record->getMessage(); // do something } } } ``` Learn more about using SNS in `serverless.yml` [in the Serverless Framework documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/sns.md). --- Source: https://bref.sh/docs/use-cases/dynamodb # DynamoDB events To handle [DynamoDB events](https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html), extend the `DynamoDbHandler` class: ```php use Bref\Context\Context; use Bref\Event\DynamoDb\DynamoDbEvent; use Bref\Event\DynamoDb\DynamoDbHandler; class MyHandler extends DynamoDbHandler { public function handleDynamoDb(DynamoDbEvent $event, Context $context): void { foreach ($event->getRecords() as $record) { $keys = $record->getKeys(); $old = $record->getOldImage(); $new = $record->getNewImage(); // do something } } } ``` Learn more about using DynamoDB in `serverless.yml` [in the Serverless Framework documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/streams.md). --- Source: https://bref.sh/docs/use-cases/kinesis # Kinesis stream processing To handle [Kinesis events](https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html), extend the `KinesisHandler` class: ```php use Bref\Context\Context; use Bref\Event\Kinesis\KinesisEvent; use Bref\Event\Kinesis\KinesisHandler; class Handler extends KinesisHandler { public function handleKinesis(KinesisEvent $event, Context $context): void { foreach ($event->getRecords() as $record) { $data = $record->getData(); // do something } } } ``` Learn more about using Kinesis in `serverless.yml` [in the Serverless Framework documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/events/streams.md). --- Source: https://bref.sh/docs/use-cases/kafka # Kafka events To handle [Kafka events](https://docs.aws.amazon.com/lambda/latest/dg/with-kafka.html), extend the `KafkaHandler` class: ```php use Bref\Context\Context; use Bref\Event\Kafka\KafkaEvent; use Bref\Event\Kafka\KafkaHandler; class Handler extends KafkaHandler { public function handleKafka(KafkaEvent $event, Context $context): void { foreach ($event->getRecords() as $record) { $data = $record->getValue(); // do something } } } ``` --- Source: https://bref.sh/docs/use-cases/custom-architecture # Custom AWS architecture The use cases detailed in the menu are just examples to get started. It is possible to combine AWS services to create a custom architecture that fits your needs. Here are some examples we've seen over the years: - Connect API Gateway straight to SQS to create an infinitely scalable asynchronous endpoint - Use CloudFront Functions to process incoming requests and route them to different origins - Use API Gateway API keys and quotas to sell access to your API - Use CloudFront, S3, and Lambda to automatically resize images on the fly - Subscribe Lambda to CloudWatch Logs to process logs in real-time - Switch to ALB or CloudFront to optimize costs - Generate PDFs on the fly with Lambda and Puppeteer - And many more... If you want to work with experienced AWS architects to design or optimize your architecture, [get in touch](mailto:matthieu@bref.sh). --- Source: https://bref.sh/docs/environment/serverless-yml # serverless.yml Your application is deployed using the Serverless framework based on the `serverless.yml` configuration file. This page introduces a few advanced concepts of the `serverless.yml` format. You can learn more in the [`serverless` documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/guides). ## Overview ```yml filename="serverless.yml" service: app provider: name: aws plugins: - ./vendor/bref/bref functions: foo: handler: index.php runtime: php-84 resources: Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: 'my-bucket' ``` ## Service ```yml service: app ``` The [service](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/services.md) is simply the name of your project. Since Serverless lets us deploy a project in [multiple stages](https://bref.sh/docs/deploy#environments) (prod, dev, staging…), CloudFormation stacks will contain both the service name and the stage: `app-prod`, `app-dev`, etc. ## Provider ```yml provider: name: aws ``` Bref only supports the `aws` provider, even though Serverless can deploy applications on other cloud providers like Google Cloud, Azure, etc. ```yml provider: name: aws # The AWS region in which to deploy (us-east-1 by default) region: us-east-1 # The stage of the application, e.g. dev, prod, staging… ('dev' by default) stage: dev ``` The `provider` section also lets us configure global options on all functions: ```yaml provider: name: aws runtime: php-84 timeout: 10 functions: foo: handler: foo.php bar: handler: bar.php # ... ``` is the same as: ```yaml provider: name: aws functions: foo: handler: foo.php runtime: php-84 timeout: 10 bar: handler: bar.php runtime: php-84 timeout: 10 # ... ``` ## Plugins ```yaml plugins: - ./vendor/bref/bref ``` [Serverless plugins](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/plugins/README.md) are JavaScript plugins that extend the behavior of the Serverless framework. Bref provides a plugin via the Composer package, which explains why the path is a relative path into the `vendor` directory. This plugin provides [support for the Bref runtimes and layers](https://bref.sh/docs/runtimes/#usage), so it is necessary to include it. Most other Serverless plugins [are installed via `npm`](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/plugins/README.md). ## Exclusions It is possible to exclude directories from being deployed via the `package.patterns` section: ```yaml package: patterns: - '!node_modules/**' - '!tests/**' ``` This has the following benefits: - faster deployments - less risk of hitting [Lambda's size limit](https://docs.aws.amazon.com/lambda/latest/dg/limits.html) - [faster cold starts](https://bref.sh/docs/environment/performances) Read more about the `package` configuration [in the serverless.yml documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/packaging.md#patterns). ## Functions ```yaml functions: foo: handler: foo.php runtime: php-84 bar: handler: bar.php runtime: php-84 ``` Functions are AWS Lambda functions. You can find all options available [in this Serverless documentation page](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/functions.md). Note that it is possible to mix PHP functions with functions written in other languages in the same `serverless.yml` config. ### Permissions If your lambda needs to access other AWS services (S3, SQS, SNS…), you will need to add the proper permissions via the `iam.role.statements` section. Read more about [AWS credentials in the documentation](https://bref.sh/docs/environment/aws-credentials). ## Stage parameters Stage parameters are a great way to define values that change depending on the stage (dev, prod, staging…). ```yaml params: # Default parameters that apply to all stages default: # Here we use the special `sls:stage` variable # to define a domain that changes depending on the stage domain: ${sls:stage}.preview.myapp.com # Parameters that apply to the prod stage prod: domain: myapp.com # Parameters that apply to the dev stage dev: domain: preview.myapp.com # Parameters can be used via the ${param:XXX} variables: provider: environment: APP_DOMAIN: ${param:domain} ``` Read the full [Serverless documentation about stage parameters](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/parameters.md#stage-parameters). ## Resources ```yaml resources: Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: 'my-bucket' ``` The `resources` section contains raw [CloudFormation syntax](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-reference.html). This lets us define any kind of AWS resource other than Lambda functions. Read more in the [Serverless documentation about resources](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/resources.md). Be careful, the CloudFormation resources must be defined in the `resources.Resources` sub-section: ```yaml resources: Resources: # ... ``` ### CloudFormation functions The CloudFormation `!Ref`, `!GetAtt` and `!Sub` functions can be used. Here is an example where we define an S3 bucket and a policy that references it. It uses both the `!Ref MyBucket` and `!Sub '${MyBucket.Arn}'` syntaxes: ```yml filename="serverless.yml" #... resources: Resources: MyBucket: Type: AWS::S3::Bucket # IAM policy that makes the bucket publicly readable MyBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref MyBucket PolicyDocument: Statement: - Effect: Allow Principal: '*' # everyone Action: s3:GetObject Resource: !Sub '${MyBucket.Arn}/*' ``` --- Source: https://bref.sh/docs/environment/variables # Environment variables Environment variables are the perfect solution to configure the application (as recommended in the [12 factor guide](https://12factor.net/config)). ## Definition Environment variables can be defined in `serverless.yml`. To define an environment variable that will be available in **all functions** declare it in the `provider` section: ```yml filename="serverless.yml" provider: # ... environment: MY_VARIABLE: 'my value' ``` To define an environment variable that will be available in **a specific function** declare it inside the function's properties: ```yml filename="serverless.yml" functions: foo: # ... environment: MY_VARIABLE: 'my value' ``` > [!WARNING] > > Do not store secret values in `serverless.yml` directly. Check out the next section to handle secrets. ## Secrets Secrets (API tokens, database passwords, etc.) should not be defined in `serverless.yml` or committed into your git repository. Instead, you can use the [SSM parameter store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html), a free service provided by AWS. ### Creating secrets Create secrets via [Bref Cloud](https://bref.cloud). If you have **not** deployed the application yet, go to the root "Secrets" section in Bref Cloud and create a new secret there ([bref.cloud/secrets/create](https://bref.cloud/secrets/create)). Provide the target application and environment name when creating the secret. If you have already deployed the application, open the application in Bref Cloud, go to a specific environment, and create a new secret under the "Secrets" tab: ![](https://bref.sh/docs/environment/variables-create-secret.png) You can also run the `bref secret:create` command in your terminal: ```bash bref secret:create ``` You can also run the command outside of a project: `bref secret:create --app=app-name --env=env-name --team=team-slug`. Create a parameter via the [AWS SSM console](https://console.aws.amazon.com/systems-manager/parameters) or the `aws` CLI: ```bash aws ssm put-parameter --region us-east-1 --name '/my-app/my-parameter' --type String --value 'mysecretvalue' ``` On Windows, the first part of the path needs to be double slashes and all subsequent forward slashes changed to backslashes: ```bash aws ssm put-parameter --region us-east-1 --name '//my-app\my-parameter' --type String --value 'mysecretvalue' ``` It is recommended to prefix the parameter name with your application name, for example: `/my-app/my-parameter`. SSM also allows storing a SecureString parameter, which is encrypted with AWS KMS. To use a SecureString, simply change the `--type` argument to `--type SecureString`. Bref takes care of decrypting the value. ### Retrieving secrets You can inject a secret in an environment variable: - either at **deployment time** (simplest) - or at **runtime** (more secure) #### At deployment time Use the [`${ssm:}` syntax](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/variables.md#reference-variables-using-the-ssm-parameter-store) to have the variable be replaced by the secret value on deployment: ```yml filename="serverless.yml" provider: # ... environment: MY_PARAMETER: ${ssm:/my-app/my-parameter} # If you need to set a different value per stage: OTHER_PARAMETER: ${ssm:/my-app/${sls:stage}/my-parameter} ``` Advantages: - Simpler, it just works. Disadvantages: - The user deploying must be allowed to retrieve the secret value. - The secret value will be set in clear text in the Lambda function configuration (anyone who can access the function can also view the value). #### At runtime Alternatively, Bref can fetch the secret values at runtime when the Lambda function starts (aka the "cold start"). To use that feature, we **must install** the `bref/secrets-loader` package: ```bash composer require bref/secrets-loader ``` To use it, the environment variable should contain the path to the SSM parameter prefixed with `bref-ssm:`. We also need to authorize Lambda to retrieve the parameter. For example: ```yml filename="serverless.yml" provider: # ... environment: MY_PARAMETER: bref-ssm:/my-app/my-parameter iam: role: statements: # Allow our Lambda functions to retrieve the parameter from SSM - Effect: Allow Action: ssm:GetParameters Resource: 'arn:aws:ssm:${aws:region}:${aws:accountId}:parameter/my-app/my-parameter' # If you want to be more generic you can uncomment the line below instead. # But it authorizes retrieving *any* SSM parameter, which is less secure. #Resource: '*' ``` On a cold start, Bref automatically checks all environment variables that start with `bref-ssm:` and will resolve the values by calling the AWS SSM API. It adds a very small latency overhead for the first request (note: all SSM values are fetched in a single API call). Advantages: - The value doesn't have to be accessible by the user deploying. - The value is not stored in plain text in the AWS console. Disadvantages: - More complex configuration. - Small added latency to cold starts. ### An alternative: AWS Secrets Manager As an alternative, you can store secrets in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). This solution, while very similar to SSM, will provide: - better permission management using IAM - JSON values, allowing you to store multiple values in one parameter However, Secrets Manager is not free: [pricing details](https://aws.amazon.com/secrets-manager/pricing/). SSM is good enough for most projects. ## Local development When [developing locally using `serverless bref:local`](https://bref.sh/docs/local-development), you can set environment variables using bash: ```bash VAR1=val1 VAR2=val2 serverless bref:local -f # Or using `export`: export VAR1=val1 export VAR2=val2 serverless bref:local -f ``` ## Learn more While this page mentions environment variables, `serverless.yml` allows other types of variables to be used. Read the [`serverless.yml` variables](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/variables.md) documentation to learn more. --- Source: https://bref.sh/docs/environment/php # Configuring PHP ## php.ini PHP will read its configuration from: - `/opt/bref/etc/php/php.ini` (PHP's official production configuration) - `/opt/bref/etc/php/conf.d/bref.ini` (Bref's optimizations for Lambda) These files *cannot be customized*. ### Customizing php.ini You can create your own `php.ini` to customize PHP's configuration: 1. create a `php/conf.d/` subdirectory in your project 1. create a `php.ini` file inside that directory _(the name of the file does not matter, it must have an `.ini` extension)_ PHP will automatically include any `*.ini` file found in `php/conf.d/` in your project. ### Customizing php.ini using a custom path If you want PHP to scan a different directory than `php/conf.d/` in your project, you can add it with the [`PHP_INI_SCAN_DIR`](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan) environment variable: ```yaml filename="serverless.yml" provider: environment: PHP_INI_SCAN_DIR: '/opt/bref/etc/php/conf.d:/var/task/my/different/dir' ``` > [!WARNING] > > Keep `/opt/bref/etc/php/conf.d` in the value so that Bref continues to load its configuration. > Setting only your custom directory would prevent `/opt/bref/etc/php/conf.d/bref.ini` from > loading. Learn how to declare environment variables by reading the [Environment Variables](https://bref.sh/docs/environment/variables) guide. ### Customizing php.ini in extra layers If you are using Lambda layers, for example to use custom PHP extensions, you can override the default `php.ini` by placing your own configuration file in `/opt/bref/etc/php/conf.d/`. Make sure to give a unique name to your `.ini` file to avoid any collision with other layers. ## Extensions Bref strives to include the most common PHP extensions. If a major PHP extension is missing please open an issue to discuss it. ### Built-in extensions The following extensions are installed and enabled by default in Bref runtimes: ### Extensions installed but disabled by default The following extensions are installed in Bref runtimes, but disabled by default: - **[intl](https://www.php.net/manual/en/book.intl.php)** - Internationalization extension (referred to as Intl) is a wrapper for ICU library, enabling PHP programmers to perform various locale-aware operations. - **[APCu](https://www.php.net/manual/en/book.apcu.php)** - APCu is APC stripped of opcode caching. - **[Redis](https://github.com/phpredis/phpredis)** - A PHP extension for interfacing with Redis. - **[soap](https://www.php.net/manual/en/book.soap.php)** - SOAP client and server for PHP You can enable these extensions by loading them in `php/conf.d/php.ini` (as mentioned in [the section above](#phpini)), for example: ```ini filename="php/conf.d/php.ini" extension=intl extension=apcu extension=redis extension=soap ``` ### Extra extensions Due to space limitations in AWS Lambda, Bref runtimes cannot include every possible PHP extension. These additional PHP extensions can be included as separate AWS Lambda layers. All extra PHP extensions are found in [brefphp/extra-php-extensions](https://github.com/brefphp/extra-php-extensions). Contributions to add more PHP extensions are welcome. ### Custom extensions It is also possible to provide your own extensions via [custom AWS Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html). > This guide is really raw, feel free to contribute to improve it. To create your custom layer, you will need to: - compile the extension (and any required libraries) in the same environment as AWS Lambda and Bref - include the compiled extension (and required libraries) in a layer - upload the layer to AWS Lambda - include the layer in your project - enable the extension in a custom `php.ini` To compile the extension, Bref provides the `bref/build-php-*` Docker images. Here is an example with Blackfire: ```dockerfile FROM bref/build-php-84:3 RUN curl -A "Docker" -o /tmp/blackfire.so -L -s "https://packages.blackfire.io/binaries/blackfire-php/1.42.0/blackfire-php-linux_amd64-php-84.so" # Build the final image from the amazon image that is close to the production environment FROM public.ecr.aws/lambda/provided:al2023 # Copy things we installed to the final image COPY --from=0 /tmp/blackfire.so /opt/bref-extra/blackfire.so ``` The `.so` extension file can then be retrieved in `/opt/bref-extra/blackfire.so`. If you installed system libraries, you may also need to copy them to the `public.ecr.aws/lambda/provided:al2023` image. See [brefphp/extra-php-extensions](https://github.com/brefphp/extra-php-extensions) for more examples. ## Custom vendor path Bref automatically requires vendor dependencies from the default `vendor/autoload.php` path. If your Composer dependencies are installed elsewhere, you can customize that path via the `BREF_AUTOLOAD_PATH` environment variable. ```yml filename="serverless.yml" provider: # ... environment: BREF_AUTOLOAD_PATH: '/var/task/foo-bar/vendor/autoload.php' ``` The path must start with `/var/task`, which is the directory where projects are installed on AWS Lambda. --- Source: https://bref.sh/docs/environment/storage # Storage on AWS Lambda Here is a simplified overview of the filesystem on AWS Lambda: ```bash /opt/ # Where Lambda runtimes and layers (like Bref) are unzipped /var/task/ # Where your application code is unzipped /tmp/ # Temporary files ... ``` The `/var/task` directory is [limited to 250MB](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html#function-configuration-deployment-and-execution). If you hit that limit, you can deploy [via Docker images instead](https://bref.sh/docs/deploy/docker). The filesystem on AWS Lambda **is read-only**, except for the `/tmp` directory. On top of that, the filesystem is not shared between instances of a lambda when it scales up. For example a file `/tmp/foo.json` will not be shared across instances of the same lambda. Since a lambda can scale up or down at any time, data in the `/tmp` directory can be lost. ## Application data Application data **must not** be stored in `/tmp` because of the behavior described above. Instead, data can be stored in [databases](https://bref.sh/docs/environment/database) or in storage services like AWS S3. ### S3 storage It is possible to deploy an S3 bucket in `serverless.yml` using the [`Storage` feature of the Lift plugin](https://github.com/getlift/lift/blob/master/docs/storage.md). For example: ```yml filename="serverless.yml" provider: environment: BUCKET_NAME: ${construct:reports-bucket.bucketName} constructs: reports-bucket: type: storage allowAcl: true ``` The [`allowAcl: true` configuration](https://github.com/getlift/lift/blob/master/docs/storage.md#acl-support) is needed because S3 buckets have ACLs disabled by default since April 2023. Many tools and libraries (including PHP's Flysystem, used by Laravel) send ACL headers on S3 operations, which will fail on buckets with ACLs disabled. The `allowAcl: true` setting lets the bucket accept these headers without errors. Note that files in the bucket are still completely private, there is no change in the security of the bucket. Read more [in the Lift documentation](https://github.com/getlift/lift/blob/master/docs/storage.md). If you use Laravel, check out the [Laravel file storage documentation](https://bref.sh/docs/laravel/file-storage) for a complete guide including presigned uploads, CORS configuration, and common pitfalls. ## Application cache Performance-wise, using AWS S3 for storing the application cache is not ideal. The following solutions can be used instead: - pre-generate the cache in the project directory before deploying - store the cache into the `/tmp` directory - store the cache into a distributed cache service like Memcache, Redis or DynamoDB (an AWS service which fits the pay-per-request model nicely) ### Pre-generating the cache Some frameworks allow pre-generating some caches. For example in Symfony the container can be compiled via `bin/console cache:warmup`, or in Laravel the config cache can be generated before deploying. When possible **this is the best solution**: no generation will occur in production, and reading from the filesystem will be fast. ### Store in the `/tmp` directory Some framework or library caches must be written into files. In that case storing in the `/tmp` directory is a good solution. Remember that anything stored in `/tmp` will be lost when a lambda stops. When a lambda starts, the `/tmp` directory will be empty so the cache will be generated again. Note that this is useful for deployments: no need to clear caches on deployments since a new version of the lambda will run on new instances (with an empty `/tmp` directory). This solution is ideal when the cached data is fast to generate and never changes (e.g. template caching, framework caches). ### Store in a distributed cache service Using a distributed cache service has the following advantages: - the cache is not lost when the lambda scales down - the cache is not lost when deploying - the cache is shared between all lambda instances The disadvantage is that if the data format changes between deployments, then a deployment strategy must be used to either clear the cache and regenerate it, or separate the cache between application versions. Cache services that can be used include for example Redis, Memcache or DynamoDB. AWS offers those as managed services through AWS ElastiCache (Redis, Memcache) or DynamoDB. Note that Redis and Memcache (through ElastiCache) run even when not used, which incurs costs. DynamoDB is a little slower than both of those but can be deployed in a "pay-per-request" mode where costs are proportional to the usage. There is a package implementing the PSR cache interfaces using DynamoDB ([rikudou/psr6-dynamo-db](https://github.com/RikudouSage/DynamoDbCachePsr6)). This solution is ideal for cache data that can change during the life of the application (e.g. caching a website menu, an API response…). #### Deploying DynamoDB tables Like any service, DynamoDB tables can be deployed via CloudFormation using the `resources` key in `serverless.yml`: ```yml filename="serverless.yml" service: app ... resources: Resources: CacheTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: # only keys are defined here, other attributes are dynamic - AttributeName: id # adds a mandatory id field AttributeType: S # the type of id is a string BillingMode: PAY_PER_REQUEST # billed for each request instead of paying for a constant capacity TimeToLiveSpecification: # deletes cache keys automatically based on a ttl field which contains a timestamp AttributeName: ttl Enabled: true KeySchema: - AttributeName: id KeyType: HASH # the type of key, HASH means partition key (similar to primary keys in SQL) ``` We need to allow code in Lambda functions to access DynamoDB. We can also pass the table name as an environment variable to the application. ```yml filename="serverless.yml" service: app provider: iam: role: statements: - Effect: Allow Resource: !GetAtt CacheTable.Arn Action: - dynamodb:DescribeTable - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem environment: # This environment variable will contain the table name DYNAMODB_CACHE_TABLE: !Ref CacheTable ``` --- Source: https://bref.sh/docs/environment/logs # Logs As explained in the [storage documentation](https://bref.sh/docs/environment/storage), the filesystem on AWS Lambda is: - read-only, except for `/tmp` - not shared between lambda instances - not persistent Because of that, logs should not be stored on disk. ## CloudWatch Instead of storing logs on disk, logs should be pushed to [AWS CloudWatch](https://aws.amazon.com/cloudwatch/), AWS' service for logs. ### Writing logs By default, Bref will forward low-level PHP errors and warnings to CloudWatch. For all other logs, your application should write logs to CloudWatch: - [With the PHP-FPM runtime for web apps](https://bref.sh/docs/runtimes/fpm-runtime): write logs to `stderr` - [With the runtime for event-driven functions](https://bref.sh/docs/runtimes/function): write logs to `stdout` (using `echo` for example) or `stderr` AWS Lambda has a built-in mechanism to forward logs written to `stderr` (or `stdout` for event-driven functions) to CloudWatch Logs in the background, **without performance impact**. If you use Laravel, Bref will automatically configure Laravel to log to CloudWatch via `stderr` (`LOG_CHANNEL=stderr`) with the `Bref\Monolog\CloudWatchFormatter` formatter. You don't have to do anything. If you have a custom log setup (e.g. using the `stack` channel), you should ensure that the `stderr` channel is included in your stack with the `Bref\Monolog\CloudWatchFormatter` formatter. With this formatter, logs will contain structured data that can be filtered in CloudWatch Logs Insights. For example, you can filter by log level or exception class. If you use Symfony, Bref will automatically configure Symfony to log to CloudWatch via `stderr` with the `Bref\Monolog\CloudWatchFormatter` formatter. You don't have to do anything. If you have a custom log setup, you should ensure that logs are sent to `stderr` (e.g. using the `stream` handler with `php://stderr`) with the `Bref\Monolog\CloudWatchFormatter` formatter (`bref.cloudwatch_formatter` service). With this formatter, logs will contain structured data that can be filtered in CloudWatch Logs Insights. For example, you can filter by log level or exception class. You can use [Monolog](https://github.com/Seldaek/monolog) to write logs to CloudWatch via `stderr`: ```php $log = new Monolog\Logger('default'); $log->pushHandler(new StreamHandler('php://stderr', Logger::INFO)); $log->warning('This is a warning!'); ``` Bref provides a formatter optimized for CloudWatch, it is highly recommended to use it: ```bash composer require bref/monolog-bridge ``` ```php $log = new Monolog\Logger('default'); $handler = new StreamHandler('php://stderr', Logger::INFO); $handler->setFormatter(new Bref\Monolog\CloudWatchFormatter); $log->pushHandler($handler); $log->warning('This is a warning!'); ``` For simple needs, you can replace Monolog with [Bref's logger](https://github.com/brefphp/logger), a PSR-3 logger designed for AWS Lambda: ```php $log = new \Bref\Logger\StderrLogger(); $log->warning('This is a warning!'); ``` ### Reading logs You can view, search and tail logs in the [Bref Cloud](https://bref.sh/cloud) dashboard: If you don't use Bref Cloud, you can view logs in the [CloudWatch console](https://console.aws.amazon.com/cloudwatch/home#logs:). You can also use `serverless logs` to view them in the terminal: ```bash serverless logs -f # Tail logs: serverless logs -f --tail ``` --- Source: https://bref.sh/docs/environment/database # Using a database AWS offers the [RDS](https://aws.amazon.com/rds/) service to run MySQL and PostgreSQL databases. Here are some of the database services offered by RDS: - MySQL - PostgreSQL - [Aurora MySQL/PostgreSQL](https://aws.amazon.com/rds/aurora/): AWS-managed database compatible with MySQL or PostgreSQL - [Aurora Serverless v2 MySQL/PostgreSQL](https://aws.amazon.com/rds/aurora/serverless/): similar to Aurora but scales automatically on-demand > [!TIP] > > Aurora Serverless can be configured to scale down to 0 when unused (which costs $0), however be careful with this option: the database can take up to 15 seconds to un-pause. ## Internet-accessible vs VPC databases | | Internet-accessible | VPC (private network) | |---|---|---| | Security | Password only | Network-isolated + password | | Best for | Non-critical projects | Projects requiring strong isolation | | Complexity | Simple | Requires VPC + NAT Gateway | | Extra cost | None | ~$33/month for NAT Gateway | | Access from your machine | Direct connection | Via SSH tunnel (e.g. [7777](https://port7777.com)) | An internet-accessible database is the simplest option and works well for most projects. Use a VPC database when you need stronger network isolation. ## Internet-accessible databases In the Bref Cloud dashboard, open the "Databases" page and [click "Create database"](https://bref.cloud/databases/create). Bref Cloud can create classic RDS databases as well as serverless databases ([Aurora Serverless v2](https://aws.amazon.com/rds/aurora/serverless/)), in both MySQL and PostgreSQL flavors. Serverless databases scale automatically with traffic and can auto-pause when unused. Fill in the form and click "Create": Bref Cloud create database form The configuration with credentials securely stored in SSM will be displayed once the database is created. In the [RDS console](https://console.aws.amazon.com/rds/home): - switch to the region of your application - click "Create database" - select the type of database you want to create (engine, instance class, etc.) and fill the rest of the form - make sure to select "Public access: Yes" Once the database is created, make sure the security group allows inbound connections on the database port from any IP address (AWS Lambda IPs are dynamic). Copy the endpoint (hostname) and configure your PHP application to connect to it. Don't forget to [securely store the username and password in AWS SSM](https://bref.sh/docs/environment/variables). Tips to better control costs: - for non-critical databases you can disable replication - switch storage to "General Purpose (SSD)" for lower costs - you can disable "enhanced monitoring" to avoid the associated costs ## VPC databases (private network) Bref Cloud handles VPC creation, subnets, security groups, and NAT Gateway configuration for you. **1. Create a network** in the Bref Cloud dashboard: Bref Cloud create network The network includes a VPC, subnets, security groups, and a NAT Gateway (~$33/month, shown in the dashboard). A single network can be reused across multiple applications and databases. **2. Create a database** and select the network you created. **3. Deploy** — Bref Cloud automatically adds the VPC configuration to your deployment. No changes to `serverless.yml` are needed. > [!WARNING] > > Lambda functions in a VPC **lose internet access**. This can cause timeouts. You need a NAT Gateway to restore internet access (~$33/month). Alternatively, you can use [a NAT instance](https://fck-nat.dev/) (from $3/month). You can follow [this tutorial](https://medium.com/@philippholly/aws-lambda-enable-outgoing-internet-access-within-vpc-8dd250e11e12) to set up a NAT Gateway, use [the serverless VPC plugin](https://github.com/smoketurner/serverless-vpc-plugin), or use the complete example in [Serverless Visually Explained](https://serverless-visually-explained.com/). You can use one VPC and one NAT Gateway for multiple projects. You can also access some AWS services (S3, SQS, etc.) without a NAT Gateway via [private VPC endpoints](https://docs.aws.amazon.com/en_pv/vpc/latest/userguide/vpc-endpoints-access.html), but this does not replace a NAT Gateway for external APIs. ### Creating a database In the [RDS console](https://console.aws.amazon.com/rds/home): - click "Create database" - select the type of database you want to create and fill in the form - for a simpler configuration leave the default VPC in the last step Tips to better control costs: - for non-critical databases you can disable replication - switch storage to "General Purpose (SSD)" for lower costs - you can disable "enhanced monitoring" to avoid the associated costs ### Connecting Lambda to the VPC To retrieve the information needed to let AWS Lambda access the database, go into [the RDS dashboard](https://console.aws.amazon.com/rds/home#databases:) and open the database you created. > [!NOTE] > > It may take a few minutes for the database to be created. Find: - The **endpoint** (hostname of the database, available after creation completes). - The **security group ID** (in the "VPC security groups" section), e.g. `sg-03f68e1100481622b`. - The list of **subnets**, e.g. `subnet-12f4130e` (there are several, one per availability zone). Add the VPC configuration to `serverless.yml` ([documentation](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/functions.md#vpc-configuration)): ```yml filename="serverless.yml" functions: hello: # ... vpc: securityGroupIds: - sg-03f68e1100481622b subnetIds: - subnet-12f4130e - subnet-c5fe33e5 - subnet-11aa85dc - subnet-85dcf240 ``` ### Authorizing Lambda connections You need to allow Lambda to connect to the database's security group: - open the database in RDS and click the security group - in the "Inbound" tab click "Edit" - add a rule: select MySQL/Aurora (or PostgreSQL) and set a "custom" source: select the security group itself (type `sg-` and use the autocompletion) - save Learn more in the AWS documentation about [configuring a Lambda to access resources in a VPC](https://docs.aws.amazon.com/lambda/latest/dg/vpc.html). ## Connecting from PHP [Bref Cloud](https://bref.sh/cloud) automatically creates SSM parameters for your database credentials and provides the environment variables to set up. If you are not using Bref Cloud, store credentials in SSM parameters ([read more](https://bref.sh/docs/environment/variables#secrets)) and configure the environment variables in `serverless.yml`: ```yml filename="serverless.yml" provider: environment: DB_HOST: DB_DATABASE: my_database DB_USERNAME: ${ssm:/my-app/db-username} DB_PASSWORD: ${ssm:/my-app/db-password} ``` Laravel reads these environment variables automatically to configure the database connection. ```yml filename="serverless.yml" provider: environment: DATABASE_URL: mysql://${ssm:/my-app/db-username}:${ssm:/my-app/db-password}@/my_database ``` Doctrine is auto-configured by Symfony using the `DATABASE_URL` environment variable. Connect to the database using the endpoint, for example with PDO: ``` mysql://user:password@dbname.e2sctvp0nqos.us-east-1.rds.amazonaws.com/dbname ``` Also refer to the [Extensions](https://bref.sh/docs/environment/php#extensions) section to see if you need to enable any database-specific extensions. ## Running database migrations ```bash bref command "migrate --force" ``` ```bash serverless bref:cli --args="migrate --force" ``` ```bash bref command "doctrine:migrations:migrate --no-interaction" ``` ```bash serverless bref:cli --args="doctrine:migrations:migrate --no-interaction" ``` You can run any CLI script via the console runtime. Read the [Console runtime documentation](https://bref.sh/docs/runtimes/console) to learn more. > [!TIP] > > A console function must be defined in your `serverless.yml`. Check the "Getting started" guide corresponding to your framework for an example. ## Accessing the database from your machine **Internet-accessible databases** can be connected to directly from your machine using tools like TablePlus, DBeaver, or any database client. **VPC databases** cannot be accessed from the internet directly. To connect from your machine, use an SSH tunnel. To create an SSH tunnel easily and securely, **[check out 7777](https://port7777.com/?utm_source=bref)**, made by Bref maintainers: 7777 - SSH tunnels to your database To expose the database publicly on the internet instead, [follow this guide](https://bref.sh/docs/environment/database-public). --- Source: https://bref.sh/docs/environment/database-planetscale # Using PlanetScale with Bref on AWS Lambda [PlanetScale](https://planetscale.com/) is a hosted serverless MySQL database based on the Vitess engine ([learn more](https://planetscale.com/docs/concepts/what-is-planetscale)). Amongst other features, it offers the following benefits compared to running a database on AWS: - Simple to set up: no VPC (virtual private network) to set up, no instances to configure. - Runs [the Vitess clustering system](https://planetscale.com/blog/vitess-for-the-rest-of-us), which offers great scalability and supports a lot more concurrent connections [via built-in connection pooling](https://planetscale.com/blog/one-million-connections). - Since it does not require a VPC, we do not need to set up and pay [for a NAT Gateway](https://bref.sh/docs/environment/database#vpc-databases-private-network). One extra feature worth mentioning is [the branching concept](https://planetscale.com/docs/concepts/branching): it enables testing schema changes before deploying them in production without downtime. [Plans](https://planetscale.com/pricing) start at $39/month for MySQL databases (PlanetScale also offers PostgreSQL databases, starting at $5/month). ## Getting started To use PlanetScale with Bref, start [by creating a PlanetScale account](https://planetscale.com/). Then, create a database in the same region as your Bref application. ![](https://bref.sh/docs/environment/database/planetscale-create.png) > [!TIP] > > The database is created with an initial development branch: `main`. PlanetScale [has a branching concept](https://planetscale.com/docs/concepts/branching) that lets you test schema changes in a development branch, then promote it to production, or even create new branches (isolated copies of the production schema) off of production to use for development. You can now click the **Connect** button and select "Connect with: PHP (PDO)". That will let you retrieve the host, database name, user and password. Here is a simple example that connects to the database using PDO and performs a few queries: ```php '; $dbname = ''; $user = ''; $password = ''; $pdo = new PDO("mysql:host=$host;dbname=$dbname", $user, $password, [ PDO::MYSQL_ATTR_SSL_CA => openssl_get_cert_locations()['default_cert_file'], ]); $pdo->exec('CREATE TABLE IF NOT EXISTS test (id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, PRIMARY KEY (id))'); $pdo->exec('INSERT INTO test (name) VALUES ("test")'); var_dump($pdo->query('SELECT * FROM test')->fetchAll()); ``` Note the `PDO::MYSQL_ATTR_SSL_CA` flag: while we connect via a username and password, [the connection happens over SSL](https://planetscale.com/docs/concepts/secure-connections) to secure against man-in-the-middle attacks. To avoid hardcoding the location of the file containing SSL certificates, we retrieve its path via `openssl_get_cert_locations()['default_cert_file']`. In Bref, the file is located here: `/opt/bref/ssl/cert.pem`. ## Laravel > [!TIP] > > This guide assumes you have already set up a Laravel application by following [the Bref documentation for Laravel](https://bref.sh/docs/laravel/getting-started). To configure Laravel to use the PlanetScale database, you need to set it up via environment variables. If you deploy a `.env` file, set up the following variables: ```bash filename=".env" DB_CONNECTION=mysql DB_HOST= DB_PORT=3306 DB_DATABASE= DB_USERNAME= DB_PASSWORD= # Connect via SSL (https://planetscale.com/docs/concepts/secure-connections) MYSQL_ATTR_SSL_CA=/opt/bref/ssl/cert.pem ``` If you don't deploy the `.env` file, you can configure the variables in `serverless.yml`: ```yml filename="serverless.yml" provider: # ... environment: DB_HOST: DB_DATABASE: DB_USERNAME: DB_PASSWORD: ${ssm:/my-app/database-password} # Connect via SSL (https://planetscale.com/docs/concepts/secure-connections) MYSQL_ATTR_SSL_CA: /opt/bref/ssl/cert.pem ``` Note that the `DB_PASSWORD` value is sensitive and can be set up as a secret via SSM. Read about [Secret variables](https://bref.sh/docs/environment/variables#secrets) to learn more. Don't forget to deploy the changes: ```bash serverless deploy ``` Now that Laravel is configured, you can run `php artisan migrate` in AWS Lambda to set up our tables: ```bash serverless bref:cli --args="migrate --force" ``` Note: foreign key constraints are disabled by default on PlanetScale. If your migrations use them, enable them in the database settings before running `php artisan migrate`. See the [MySQL compatibility section](#mysql-compatibility) for more information. That's it! Our database is ready to use. ## Symfony > [!TIP] > > This guide assumes you have already set up a Symfony application by following [the Bref documentation for Symfony](https://bref.sh/docs/symfony/getting-started). First, make sure you have installed Doctrine, or [follow these docs to do so](https://symfony.com/doc/current/doctrine.html#installing-doctrine). To configure Symfony to use the PlanetScale database, you need to set it up via environment variables. If you deploy a `.env` file, set up the following variables: ```bash filename=".env" DATABASE_URL="mysql://:@:3306/?serverVersion=8.0" ``` If you don't deploy the `.env` file, you can configure the variables in `serverless.yml`: ```yml filename="serverless.yml" provider: # ... environment: DATABASE_URL: ${ssm:/my-app/database-url} ``` Note that the `DATABASE_URL` value is sensitive and can be set up as a secret via SSM. Read about [Secret variables](https://bref.sh/docs/environment/variables#secrets) to learn more. Finally, edit the `config/packages/doctrine.yaml` configuration file to set up [the SSL connections](https://planetscale.com/docs/concepts/secure-connections): ```yml filename="config/packages/doctrine.yaml" {6} doctrine: dbal: url: '%env(resolve:DATABASE_URL)%' options: # Connect to the database via SSL !php/const PDO::MYSQL_ATTR_SSL_CA: /opt/bref/ssl/cert.pem # ... ``` Let's deploy the changes: ```bash serverless deploy ``` Now that Symfony is configured, you can run the `bin/console doctrine:migrations:migrate` command in AWS Lambda to set up our tables: ```bash serverless bref:cli --args="doctrine:migrations:migrate" ``` Note: foreign key constraints are disabled by default on PlanetScale. If your migrations use them, enable them in the database settings before running your migrations. See the [MySQL compatibility section](#mysql-compatibility) for more information. That's it! Our database is ready to use. ## MySQL compatibility PlanetScale is based on the Vitess clustering system, which was built for scaling MySQL. Because of that, Vitess doesn't support all MySQL features. The biggest difference is about **foreign key constraints**. They are [supported since February 2024](https://planetscale.com/blog/foreign-key-constraints-are-now-generally-available) on unsharded databases, but they are **disabled by default**: enable them in the database settings ("Allow foreign key constraints") if your application needs them. Read about [their limitations](https://planetscale.com/docs/vitess/foreign-key-constraints) before doing so. If you keep them disabled, it is still possible to have references between rows of different tables and perform joins. But constraints are not enforced: foreign keys are not validated at the database level, and you cannot use `ON DELETE ...` statements. That means that you should take care of validating references between rows and handle cascade deletions. If you use an ORM, you should be in a good place: - **Laravel** DB migrations and Eloquent work fine. You can use [the `foreignId()` method](https://laravel.com/docs/migrations#foreign-key-constraints) to create relationships between tables, but you cannot enforce referential integrity with the `constrained()` method (and related methods like `onDelete('cascade')`). - [**Doctrine**](https://www.doctrine-project.org/) works fine, but you should not use [`onDelete="CASCADE"` which relies on foreign key constraints](https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/working-with-objects.html#removing-entities) (`cascade=REMOVE` or `cascade=ALL` is fine as Doctrine performs the cascade in memory via PHP). You can read the [complete **MySQL compatibility table** on the PlanetScale website](https://planetscale.com/docs/reference/mysql-compatibility). ## Database import PlanetScale provides an automated import tool to import an existing database without downtime. Check out [the documentation](https://planetscale.com/docs/imports/database-imports) to get started. For simple scenarios, you can also use the [`mysqldump` tool](https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html#mysqldump-syntax) to export your existing database and import it later in PlanetScale. Note that there are [specific options you need to use for Vitess](https://vitess.io/docs/15.0/user-guides/configuration-basic/exporting-data/#mysqldump). If foreign key constraints are disabled on your PlanetScale database, you also need to export the schema and the data separately so that you can remove the constraints from the schema. Let's first export the schema and the data: ```bash mysqldump -u -p -h --set-gtid-purged=OFF --no-tablespaces --no-data > schema.sql mysqldump -u -p -h --set-gtid-purged=OFF --no-tablespaces --no-create-info > data.sql ``` Next, if foreign key constraints are disabled, edit `schema.sql` to remove them ([learn more](https://planetscale.com/docs/vitess/operating-without-foreign-key-constraints)), for example: ```diff filename="schema.sql" CREATE TABLE products ( id INT NOT NULL, category_id INT, PRIMARY KEY (id), - KEY category_id_idx (category_id), - CONSTRAINT `category_fk` FOREIGN KEY (category_id) REFERENCES category(id) + KEY category_id_idx (category_id) ); ``` (watch out for the trailing comma, else you might get errors like "You have an error in your SQL syntax") Finally, you can import the `schema.sql` and `data.sql` into PlanetScale, **using the PlanetScale settings this time** (user, password, host): ```bash mysql -u -p -h < schema.sql mysql -u -p -h < data.sql ``` ## Schema changes workflow PlanetScale has a concept of [database branches](https://planetscale.com/docs/concepts/branching): - [**Development** branches](https://planetscale.com/docs/concepts/branching#development-and-production-branches) are isolated copies of your production database and are used to test schema changes in development. - [**Production** branches](https://planetscale.com/docs/concepts/branching#development-and-production-branches) are high availability branches intended for production traffic. They are protected from direct DDL, so you cannot perform direct schema changes on production branches. You can set up production branches in two ways: - either **allow** running DB migrations directly on your production database, - or **forbid** direct schema changes in production by enabling [_Safe Migrations_](https://planetscale.com/docs/concepts/safe-migrations). ### Without "Safe Migrations" If the production branch has the "Safe Migrations" feature **disabled**, you can run DB migrations on the production database as part of your deployment. This strategy implies either: - accepting downtime on deployment, for example by using [Laravel's maintenance mode](https://bref.sh/docs/laravel/maintenance-mode) (put the app offline, deploy, run migrations, then put the app back online) - or always writing backward-compatible DB migrations This option works well for applications with low traffic or in early development. For high-traffic applications, using "Safe Migrations" is recommended instead. ### With "Safe Migrations" If the production branch has the "Safe Migrations" feature **enabled**, you need to use development branches and deploy requests. Below is an introduction to these features. You start with a development branch called `main`, which lets you set up your schema. Once set up, you can [promote](https://planetscale.com/docs/concepts/branching#promote-a-branch-to-production) that branch (or any other branch) to a production branch with "Safe Migrations" enabled. Later, you can apply schema changes (aka DB migrations) to the production database **without downtime**: 1. In PlanetScale, create a new development branch off of the production branch. This is an isolated copy of the production schema that you can freely play around with. 2. Set up a dev environment of your application. 3. Run the DB migrations in the dev branch. 4. Test the changes in the dev environment. 5. In PlanetScale, deploy the DB changes to the production branch via a "deploy request". Let's dive into these steps in the next section. #### Deploying DB migrations in detail PlanetScale works with **branches**, which matches the concept of **stages** in Serverless Framework. To deploy DB migrations in production, you can work with two environments: - A **production** environment: our application deployed in the `prod` stage and configured to use the `production` PlanetScale branch. - A **dev** environment: our application deployed in the `dev` stage and configured to use the `development` PlanetScale branch. You can [deploy our applications to different stages](https://bref.sh/docs/deploy#environments) via the `--stage` option. Each stage is completely isolated from the others. ```bash # Deploy the "dev" environment: serverless deploy # Deploy the "prod" environment: serverless deploy --stage=prod ``` You want each "stage" of our application to connect to a different PlanetScale branch. You can achieve that in `serverless.yml` via [stage parameters](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/parameters.md#stage-parameters): ```yml filename="serverless.yml" provider: # ... environment: # ... DB_HOST: DB_DATABASE: DB_USERNAME: ${param:db-user} DB_PASSWORD: ${param:db-password} params: # These values apply for all stages by default default: # Connect to the "development" database in PlanetScale db-user: db-password: ${ssm:/my-app/dev/db-password} # These values apply for the `prod` stage only prod: # Connect to the "production" database in PlanetScale db-user: db-password: ${ssm:/my-app/prod/db-password} ``` In the example above, the `DB_USERNAME` and `DB_PASSWORD` environment variables will have different values based on the stage. The next step is to [create the `development` branch off of the production branch](https://planetscale.com/docs/concepts/branching#create-a-development-branch). This branch will be an isolated copy of the production schema. Now that the environments are set up, you can apply the following workflow for DB migrations: 1. Deploy your code changes and migrations in the development stage. 1. Apply DB migrations in the **development** environment (drop a column, add a table, etc.): - If you use Laravel, run DB migrations [via the `artisan` function](https://bref.sh/docs/laravel/getting-started#laravel-artisan): `serverless bref:cli --stage=dev --args="migrate"` - If you use Symfony, run DB migrations [via the `console` function](https://bref.sh/docs/symfony/getting-started#symfony-console): `serverless bref:cli --stage=dev --args="doctrine:migrations:migrate"` - If you don't use any framework, run DB queries [via the `pscale` CLI](https://planetscale.com/docs/reference/planetscale-cli). 1. Test changes in the development environment to make sure everything works correctly. 1. Create a deploy request. PlanetScale will create a schema diff that you can review before applying. It will also validate the diff and detect schema issues like missing unique keys, etc. Once reviewed and approved, you can add it to the deploy queue and PlanetScale will begin the deployment. The schema changes are deployed without downtime: no table gets locked, and production is not slowed down during the migration. This is what is called "[non-blocking schema changes](https://planetscale.com/docs/concepts/nonblocking-schema-changes)". The migrations are now applied to production. ### When to apply migrations? Depending on the schema change, you might want to apply DB migrations _before_ or _after_ a code deployment: - **Add a column/table:** apply the migration _before_ deploying the code. - **Remove a column/table:** apply the migration _after_ deploying the code. - **Rename a column/table:** this scenario is more complex and needs to be addressed in two steps: - Apply migrations that **add** the column/table (it will be duplicated). - Deploy code changes to write new data to the new column/table, and read from both. - Run a script that copies the old data to the new column/table. - Deploy code changes to only read and write to the new column/table. - Apply migrations that **remove** the old column/table. If you use Laravel, you can read a complete blog post about this topic: [Zero downtime Laravel migrations](https://planetscale.com/blog/zero-downtime-laravel-migrations#when-to-run-migrations). --- Source: https://bref.sh/docs/environment/aws-credentials # AWS credentials on AWS Lambda When your PHP application runs on AWS Lambda, it automatically has access to AWS credentials. This means you don't need to manage AWS access keys or credentials in your code - Lambda handles this for you. > [!WARNING] > > Don't deploy AWS access keys in your Lambda functions or environment variables. Lambda provides credentials automatically. > > This is a common mistake **when migrating an existing application to AWS Lambda**. ## How it works Lambda functions **automatically get AWS access keys** in their environment variables. These credentials are temporary and managed by AWS, so you don't have to worry about rotating them or keeping them secure. ```php echo $_SERVER['AWS_ACCESS_KEY_ID']; // AKIAIOSFODNN7EXAMPLE echo $_SERVER['AWS_SECRET_ACCESS_KEY']; // wJalrXUtnFEM echo $_SERVER['AWS_SESSION_TOKEN']; // AQoEXAMPLEH4aoAH0gNCAPy... ``` The PHP AWS SDK automatically detects and uses them. Here's an example with S3: ```php $s3 = new \Aws\S3\S3Client([ 'version' => 'latest', 'region' => $_SERVER['AWS_REGION'], // No credentials needed, the SDK uses the environment variables automatically ]); // Use S3 normally $result = $s3->putObject([ 'Bucket' => 'my-bucket', 'Key' => 'file.txt', 'Body' => 'Hello from Lambda!' ]); // Note that this also works with https://async-aws.com ``` Note that **Laravel and Symfony automatically pick up these permissions** too. These credentials have access controlled by an IAM role defined in `serverless.yml`. > [!NOTE] > > By default, Lambda functions **don't have any access** (principle of least privilege). To access other AWS services (like S3 or SQS), you need to add permissions to that IAM role in `serverless.yml` (read below). ## Adding permissions To grant your Lambda function access to AWS services, add IAM statements to your `serverless.yml`: ```yaml service: my-app provider: name: aws iam: role: statements: # IAM statements here... functions: # ... ``` ### Example: S3 To read and write files to an S3 bucket: ```yaml provider: name: aws iam: role: statements: # Allow Lambda to read and write to S3 - Effect: Allow Action: - s3:GetObject - s3:PutObject - s3:DeleteObject Resource: arn:aws:s3:::my-bucket/* # Allow listing bucket contents - Effect: Allow Action: s3:ListBucket Resource: arn:aws:s3:::my-bucket ``` > [!TIP] > > If you use the [Lift `storage` construct](https://bref.sh/docs/environment/storage#s3-storage) to create S3 buckets, it [automatically adds the necessary permissions](https://github.com/getlift/lift/blob/master/docs/storage.md#permissions) to your functions. No need to set up permissions manually! ### Example: SQS To send and receive messages from SQS queues: ```yaml provider: name: aws iam: role: statements: # Allow Lambda to access an SQS queue - Effect: Allow Action: - sqs:SendMessage - sqs:ReceiveMessage - sqs:DeleteMessage - sqs:GetQueueAttributes Resource: arn:aws:sqs:${aws:region}:${aws:accountId}:my-queue ``` > [!TIP] > > If you use the [Lift `queue` construct](https://bref.sh/docs/use-cases/sqs#creating-sqs-queues) to create SQS queues, it [automatically adds the necessary permissions](https://github.com/getlift/lift/blob/master/docs/queue.md#permissions) to your functions. No need to set up permissions manually! ## Common services and permissions Here are the IAM actions you'll typically need for common AWS services: ### DynamoDB ```yaml - Effect: Allow Action: - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:Query - dynamodb:Scan Resource: arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/my-table ``` ### Secrets Manager ```yaml - Effect: Allow Action: secretsmanager:GetSecretValue Resource: arn:aws:secretsmanager:${aws:region}:${aws:accountId}:secret:my-secret-* ``` ### SNS (notifications) ```yaml - Effect: Allow Action: sns:Publish Resource: arn:aws:sns:${aws:region}:${aws:accountId}:my-topic ``` ### EventBridge ```yaml - Effect: Allow Action: events:PutEvents Resource: arn:aws:events:${aws:region}:${aws:accountId}:event-bus/my-event-bus ``` ### SSM Parameter Store ```yaml - Effect: Allow Action: - ssm:GetParameter - ssm:GetParameters Resource: arn:aws:ssm:${aws:region}:${aws:accountId}:parameter/my-app/* ``` ## Troubleshooting ### Access Denied errors If you get "Access Denied" errors when trying to use AWS services: 1. Check that you've added the correct IAM permissions in `serverless.yml` 2. Verify the resource ARN is correct (bucket name, queue name, etc.) 3. Make sure you've redeployed after adding permissions 4. [Check the logs](https://bref.sh/docs/environment/logs) for the exact error message ### Testing locally When testing locally remember that you will need to provide AWS credentials since you're not running on Lambda. You can set them up via long-lived AWS access keys or IAM roles with SSO. ## Permissions per function If you want to define permissions **per function**, instead of globally (i.e. in the `provider` section), you can install the plugin [`serverless-iam-roles-per-function`](https://github.com/functionalone/serverless-iam-roles-per-function) and then use the `iamRoleStatements` at the function definition block. ## Learn more - [`serverless.yml` IAM guide](https://github.com/oss-serverless/osls/blob/4.x/docs/guides/iam.md) - [Documentation of the AWS SDK for PHP](https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/) --- Source: https://bref.sh/docs/environment/cold-starts # Cold starts If your application cannot tolerate any response above 300ms (e.g. real-time trading, multiplayer gaming), Lambda is not the right fit. For everything else, cold starts are often the most overestimated concern when moving to serverless. On applications with regular traffic, cold starts represent about **0.2% of requests**. For the vast majority of applications, their impact is negligible. ## What is a cold start? AWS Lambda runs code on-demand. When a new Lambda instance boots to handle a request, the initialization time is called a *cold start*. Once initialized, the instance stays warm and handles subsequent requests with no cold start. Lambda keeps instances alive for several minutes after the last request. As long as your application receives regular traffic, most requests are handled by warm instances. You can learn more about how Bref and Lambda work in [How Bref works](https://bref.sh/docs/how-it-works), and about how Lambda scales in [Serverless Visually Explained](https://serverless-visually-explained.com/). ## Cold start duration Bref's PHP runtimes add a cold start of about **250ms** on average. The rest depends on the size of your application. To put it differently: on average (application with traffic), out of 1000 requests, 998 are as fast as on a traditional server. 2 requests have an extra cold start latency. This is on par with [cold starts in other languages](https://mikhail.io/serverless/coldstarts/aws/) like JavaScript, Python or Go, and Bref's runtimes are optimized as much as possible. ## Warming for low-traffic applications If your application has very low traffic (e.g. a new project or an internal tool), your Lambda functions might scale down to 0 instances. Cold starts will then happen more often. You can pre-warm your HTTP function by adding a scheduled event in `serverless.yml`: ```yml filename="serverless.yml" functions: web: handler: public/index.php runtime: php-84-fpm timeout: 15 events: - httpApi: '*' - schedule: rate: rate(5 minutes) input: warmer: true ``` Bref recognizes the `warmer` event and responds with a `Status: 100` in a few milliseconds without executing your application code. This keeps the Lambda instance warm. You can also use external services like [Pingdom](https://www.pingdom.com/) to ping your application regularly. ## Provisioned concurrency AWS offers [provisioned concurrency](https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html) to keep a set number of Lambda instances initialized at all times. This completely eliminates cold starts for those instances. This is useful for applications that need consistently low latency but is more expensive since you pay for the instances even when they are idle. For most applications, the warming approach above is simpler and sufficient. ## Reducing cold start duration The codebase size can increase the cold start duration. When deploying, exclude unnecessary files in `serverless.yml`: ```yml filename="serverless.yml" package: patterns: - '!assets/**' - '!node_modules/**' - '!tests/**' - ... ``` Read more about this [in the serverless.yml documentation](https://bref.sh/docs/environment/serverless-yml#exclusions). --- Source: https://bref.sh/docs/environment/performances # Performance This article sums up what to expect in terms of performance and how to optimize serverless PHP applications. The benchmarks included in this page can be reproduced via [the code on GitHub](https://github.com/brefphp/benchmarks). ## CPU power and memory size The main factor affecting performance is memory size. Indeed, [the amount of memory is proportional to the CPU power available](https://docs.aws.amazon.com/lambda/latest/dg/resource-model.html). In other words, **more memory means a more powerful CPU**. A 1024M lambda has a CPU two times more powerful than a 512M lambda. From 128M to 1,769M, applications run with up to one vCPU (1,769M gives 1 full vCPU). Memory can go up to 10,240M, which provides up to 6 vCPUs. Since PHP is single-threaded and one lambda handles only 1 request at a time, using more than 1 vCPU usually does not provide any benefit. **It is recommended to use 1024M** for PHP applications, or at least to start with that. This is what Serverless deploys by default, so there is nothing to do. To customize the amount of memory, set the `memorySize` option in `serverless.yml`: ```yml filename="serverless.yml" {5} functions: foo: handler: index.php # ... memorySize: 512 # set to 512M instead of 1024M (the default) ``` In the benchmark below, we run [PHP's official `bench.php` script](https://github.com/php/php-src/blob/master/Zend/bench.php). This script is CPU-intensive. | | 128M | 512M | 1024M | 2048M | |------------------|------:|-----:|------:|------:| | Execution time | 5.7s | 1.4s | 0.65s | 0.33s | For comparison, `bench.php` runs in 1.3s on a 512M [Digital Ocean](https://www.digitalocean.com/) server, in 0.8s on a 2.8Ghz i7 and in 0.6s on a 3.2Ghz i5. It is safe to say that a 1024M lambda provides a powerful CPU. ### Costs AWS Lambda bills the number of events + the execution time. The more memory configured for a lambda, [the more expensive the execution time is](https://aws.amazon.com/lambda/pricing/). It might be tempting to lower the memory to save money. However, a function might run slower on a smaller lambda, canceling the cost savings. For example, both of these scenarios cost the same thing: - a function running in 400ms on a 512M lambda - the same function running in 200ms (because of the faster CPU) on a 1024M lambda In general, **use smaller and slower lambdas only when speed is not important at all.** ## PHP runtime overhead ### Bref for web apps The [FPM runtime for web apps](https://bref.sh/docs/runtimes/fpm-runtime) **does not add overhead to response times**. Here are execution times for an empty PHP application: | | 128M | 512M | 1024M | 2048M | |------------------|------:|-----:|------:|------:| | Execution time | 10ms | 1ms | 1ms | 1ms | Unless we use a particularly slow lambda (see the previous section, 128M is not recommended), 1ms is the same execution time when PHP runs with Apache or Nginx on a classic server. We can see the same result with a "Hello world" written in Symfony (4ms being the minimum execution time of the framework): | | 128M | 512M | 1024M | 2048M | |------------------|------:|-----:|------:|------:| | Execution time | 58ms | 4ms | 4ms | 4ms | ### Bref for event-driven functions The [runtime for event-driven functions](https://bref.sh/docs/runtimes/function) adds a small overhead: | | 128M | 512M | 1024M | 2048M | |------------------|------:|-----:|------:|------:| | Execution time | 175ms | 35ms | 16ms | 13ms | Since this runtime is often used in asynchronous scenarios (for example, processing queue messages), it is often negligible. This overhead is caused by the PHP executable starting for every new invocation. We can skip that overhead by keeping the PHP process alive: ```yml filename="serverless.yml" {5} functions: hello: # ... environment: BREF_LOOP_MAX: 100 ``` In the example above, the PHP process will restart only every 100 invocations, removing the overhead the rest of the time. In that case, be careful with clearing in-memory data between every event. > [!TIP] > > Note: the PHP process will be restarted in case of a failed invocation (PHP exception thrown in the handler). ## Cold starts On applications with regular traffic, cold starts only represent about **0.2% of requests**. Bref's PHP runtimes add a cold start of about **250ms** on average, which is on-par with other languages. Read more in the [Cold starts documentation](https://bref.sh/docs/environment/cold-starts). --- Source: https://bref.sh/docs/community # Community To report bugs you can head over to the [GitHub Bref repository](https://github.com/brefphp/bref). For community support and general discussions, [open a GitHub discussion](https://github.com/brefphp/bref/discussions). You can also join the [Slack community](https://bref.sh/slack) to discuss Bref and exchange with the community (please open an issue if the link doesn't work). On X, follow [@brefphp](https://x.com/brefphp) to get news about Bref. ## Contributors The full list of contributors to Bref is [available here](https://github.com/brefphp/bref/graphs/contributors). ## Newsletters Here are a few newsletters related to serverless and PHP. Those are not necessarily related to Bref. You can subscribe to these newsletters to keep up to date with what's happening in the serverless world. - [Serverless PHP](https://serverless-php.news/) A newsletter about serverless news related to PHP. - [Off by None](https://www.jeremydaly.com/newsletter/) A very packed weekly newsletter about serverless news in general. --- Source: https://bref.sh/docs/case-studies # Case studies This page collects case studies of serverless PHP applications built with or migrated to Bref. They help you learn about costs, performance and migration efforts from real use cases. ## Applications - [Craft Cloud](https://bref.sh/docs/case-studies/case-studies/craft-cloud) How Craft Cloud runs Craft CMS projects at scale on AWS Lambda with Bref. - [Treezor](https://bref.sh/docs/case-studies/case-studies/treezor) How Treezor, a banking platform, went from legacy code on servers to a serverless architecture with Bref. - [Spreaker](https://careers.spreaker.com/engineering/rebuilding-spreaker-web-listening-experience-with-php-and-serverless/) How Spreaker, a podcast hosting platform, rewrote a decade-old monolith with Bref, Laravel Octane and Livewire. - [externals.io](https://mnapoli.fr/serverless-case-study-externals/) A case study of the migration of [externals.io](https://externals.io/) to AWS Lambda using Bref. This includes performance and costs details. - [returntrue.win](https://mnapoli.fr/serverless-case-study-returntrue/) A case study of the development of the [returntrue.win](https://returntrue.win/) website using AWS Lambda, including a cost analysis. ## Workers - 🇫🇷 [Enoptea](https://www.enoptea.fr/serverless-et-php/) Enoptea is a French startup that migrated their infrastructure of PHP workers from EC2 to Lambda. They halved their AWS costs and increased their performance while spending less time managing their servers. - [PrettyCI.com](https://mnapoli.fr/serverless-case-study-prettyci/) PrettyCI was a SaaS providing continuous integration for PHP coding standards. Internally, it runs PHP-CS-Fixer or CodeSniffer on AWS Lambda using Bref. This article is a good introduction on how AWS Lambda can be a good solution to run workers and background jobs. - [MyBuilder](https://mybuilder.com) MyBuilder is an online marketplace matching tradespeople with home owners. They used Lambda with Bref to create a highly scalable on-demand microservice to generate PDF reports. The solution involved [creating their own layer](https://web.archive.org/web/20210505204331/https://tech.mybuilder.com/compiling-wkhtmltopdf-aws-lambda-with-bref-easier-than-you-think/) to include a self-compiled binary file to use alongside Bref's base PHP layer. - [PDF reporting generation](https://devops-life.com/blog/2020/03/06/how-serverless-saved-us-for-$2-with-bref-sh/) A case study of going serverless for PDF generation. They generated 2,000 PDFs in less than 2 min for $2 using Symfony and Bref. ## Others - [Serverless](https://www.serverless.com/category/user-stories) It isn't exactly about PHP over Serverless, but it gives us a general overview of how hundreds of teams are handling serverless across a diversity of projects. There are a lot of case studies from [US Department of Defense streamlining open source contributions](https://www.serverless.com/blog/dept-of-defense-doc-bot/) to a project which has [reduced back-end costs by 95%](https://www.serverless.com/blog/abstract-partner-program-announcement/) and much more. --- Source: https://bref.sh/docs/case-studies/craft-cloud # How Craft CMS built Craft Cloud
This case study dives into how the Craft CMS team created the Craft Cloud hosting platform using serverless technologies and Bref.
[Craft CMS](https://craftcms.com/) is an open-source PHP CMS that powers more than 150,000 websites across the world. Last week, the Craft team launched [Craft Cloud](https://craftcms.com/cloud), a scalable hosting platform for Craft CMS users. To make Craft Cloud secure and scalable while keeping infrastructure simple to maintain, the Craft team decided to **run Craft CMS serverless**. To do so, Craft Cloud is built on AWS and Cloudflare and **uses Bref to run Craft CMS (and PHP) at scale on AWS Lambda**. In this case study, let’s explore in more detail how the Craft team built their cloud. ## Architecture overview ![](https://bref.sh/docs/case-studies/craft-cloud/architecture.svg) Craft Cloud is composed of several applications: - The **Craft Cloud Console** is the UI that users can use to add, configure, and monitor their projects. The Console also allows users to create organizations, purchase plugins, and Craft licenses. - The Console talks to the **Craft Cloud API**. The API is responsible for creating and updating the infrastructure for each customer’s project and environments. - The **Craft Cloud environments** are where the customers' Craft CMS projects run. Each project runs in a separate environment based on a Git branch, ensuring projects and customers are isolated from each other. ## Craft Cloud Console ![](https://bref.sh/docs/case-studies/craft-cloud/console.png) The Craft Cloud Console is the visible part: the web UI where users can sign up, create projects, and monitor them. This is a Craft CMS application currently running on AWS ElasticBeanstalk (with plans to move onto Craft Cloud itself later). This UI talks to the Craft Cloud API to perform actions on projects. ## Craft Cloud API This API is responsible for orchestrating all the customers' Craft Cloud projects: creating projects, databases, queues, as well as deploying the code, setting environment variables, etc. ![](https://bref.sh/docs/case-studies/craft-cloud/api.svg) It is implemented as a Laravel application and runs on AWS Lambda using Bref and [its Laravel bridge](https://bref.sh/docs/laravel/getting-started). Some of its operations can take time (for example when creating new AWS resources), which is why it uses [Laravel Queues set up with SQS](https://bref.sh/docs/laravel/queues). It also uses PostgreSQL and DynamoDB as databases, and S3 to store artifacts. Each Craft Cloud project is deployed to AWS Lambda via a container image. The API builds these custom container images, one image for each project. That allows each project to run with a specific PHP version, environment variables, PHP extensions, etc. The Craft Cloud API builds the Lambda container images using [Dagger](https://dagger.io) (Dagger is like writing Dockerfiles using Go), and a custom Go application that orchestrates and inspects the builds. These builds run on ECS (Fargate), as AWS Lambda cannot build Container images directly. ## Craft Cloud environments Craft Cloud environments are the key part of the infrastructure: they run Craft CMS customer projects. These run serverless with Bref on AWS Lambda, using the container images built by the Craft Cloud API. In each environment, Craft CMS customer projects have automatic access to a MySQL or PostgreSQL database, S3 for storing files, a job queue preconfigured with SQS, commands, and logs storage in CloudWatch. Customers can also choose the AWS regions in which their application will run. ![](https://bref.sh/docs/case-studies/craft-cloud/environments.svg) ### AWS Lambda events Each customer project can run in multiple environments (dev, staging, production, etc.), each running in a single AWS Lambda function. That means that **one AWS Lambda function handles multiple events**: - HTTP invocations to serve the web app - SQS invocations to process [queue jobs](https://craftcms.com/docs/4.x/extend/queue-jobs.html) - Command invocations to manually trigger [console commands](https://craftcms.com/docs/4.x/console-commands.html) ![](https://bref.sh/docs/case-studies/craft-cloud/aws-lambda-events.svg) While unusual on AWS Lambda, the Craft team made this design choice to keep the architecture straightforward (1 function per environment), as well as simplify management. This choice has been working out successfully so far. To achieve this, the Bref runtimes [had to be customized](https://github.com/craftcms/cloud-extension-yii2/blob/1.x/src/runtime/event/EventHandler.php) so that a central “event router” dispatches AWS Lambda invocations to the correct Craft CMS handler (HTTP, queue, command, etc.). ### NATS.io When building a multi-regional application, some tasks need to be region-specific for data privacy and performance. Examples of these tasks include builds, database backups, and running Craft Commands. To support region-specific tasks, Craft Cloud uses NATS.io and Synadia Cloud to stream events to the region where the work needs to occur. ### Domain names & CDN Every Craft Cloud project gets a preview domain name in the form: `xxx.preview.craft.cloud`. Customers are then free to set up their own custom domains. To serve these dynamic preview domains, Craft Cloud uses the Cloudflare CDN, with its [SSL for SaaS](https://www.cloudflare.com/application-services/products/ssl-for-saas-providers/) feature and its [Cloudflare Workers](https://workers.cloudflare.com/). ![](https://bref.sh/docs/case-studies/craft-cloud/cdn.svg) Cloudflare Workers are serverless functions written in JavaScript and running in edge locations (unlike AWS Lambda’s default behavior which runs in data centers). Workers also come with [Workers KV](https://developers.cloudflare.com/kv/), a simple and fast key-value storage. Workers are in essence limited: they can only run a subset of JavaScript, do not get access to a filesystem, etc. But these limits allow running Workers in edge locations, close to users, without cold starts, and with very low latency. This makes them very useful for doing dynamic routing: exactly what Craft Cloud does. Whenever a new project environment is created, the Craft Cloud API creates a new Worker KV storage entry. Then, on every request to `xxx.preview.craft.cloud`, a Worker compares the domain name to the KV database, retrieves the entry for the project/environment, and invokes the AWS Lambda function that runs the Craft CMS project. The Craft team decided to go with Cloudflare instead of a solution like AWS API Gateway or CloudFront because it did not impose limits on the number of custom domains that could be set up. Another benefit of Cloudflare is that it allows caching entire HTTP responses to help serve websites faster to users. This is a key feature of Craft Cloud: static caching is enabled by default to improve latency on all hosted websites. ### Running Craft CMS and Yii on AWS Lambda Craft CMS is built on top of the [Yii PHP framework](https://www.yiiframework.com/). While Bref provides native integrations for [Laravel](https://bref.sh/docs/laravel/getting-started) and [Symfony](https://bref.sh/docs/symfony/getting-started), it does not for Yii. That means Yii had to be customized to run on AWS Lambda. The Craft team published this as an open-source package on GitHub: [github.com/craftcms/cloud-extension-yii2](https://github.com/craftcms/cloud-extension-yii2). This package pre-configures Craft CMS and Yii to: - Publish assets to S3 so that they are served by the CDN - Store data files on S3 instead of the local disk - Store temporary files in /tmp - Send background jobs to SQS - Write logs to stderr so that they are automatically collected to AWS CloudWatch It also provides helpers to reference assets served via the CDN, detect whether the app is running in Craft Cloud, and more. ## Conclusion By building on top of cloud primitives, like AWS Lambda, SQS, S3, and Cloudflare workers, the Craft team is able to build a PaaS that is secure and scalable while keeping the maintenance effort manageable. With this architecture, Craft Cloud has served over **310 million HTTP requests and 6TB of data** in the last 30 days. Having been able to help the Craft team adopt Bref and AWS Lambda over the last few years, I am personally very excited to see the project go live, and I want to congratulate them for the launch! Running PHP online deserves to be simpler, and Craft is making it happen! A huge thank you to the [Craft](https://craftcms.com/) team for being a long-term sponsor of Bref, and thank you [Jason](https://www.linkedin.com/in/jason-mccallister) for sharing that story with us! --- Source: https://bref.sh/docs/case-studies/treezor # Treezor: a serverless banking platform
This case study dives into how Treezor went serverless for their banking platform. From legacy code running on servers to a serverless monolith, and then event-driven microservices on AWS with Bref.
[Treezor](https://www.treezor.com/) is a banking-as-a-service platform that serves millions of transactions every day. **You might be using it every day** through its clients: neobanks, employee benefit cards, company travel cards, and many other financial services. Because Treezor's clients have very different use cases, the platform's infrastructure must be able **to scale and be resilient** to accommodate various usage patterns. Whether it's a luncheon voucher transaction spike at lunchtime or a monthly batch of transactions by corporate clients. On top of that, some API endpoints need to respond fast, **in near real-time**, for example to authorize live credit card payments. To build such a platform, Treezor migrated from a legacy PHP application running on servers to a serverless architecture running on AWS Lambda with Bref. They did such a migration in 3 steps: - First, they validated the serverless infrastructure by building a new service as a serverless PHP application. - Then, they did a "lift-and-shift" migration by **running the legacy PHP application on AWS Lambda**. - Finally, they slowly refactored the legacy application into multiple **PHP microservices** using the "strangler" pattern. Let's explore this serverless migration in more detail. ## The original legacy stack The original stack was a legacy PHP monolith, built with no framework, running on [OVH](https://ovh.com/) servers. That monolith was responsible for the "Core Banking" API, i.e. handling all the critical operations of the system, like authorizing credit card payments or executing bank transfers. Because it was running on servers, handling unpredictable traffic spikes was challenging. ## Validating the serverless infrastructure Migrating a Core Banking system to a new infrastructure is not something you do every day. To make sure that AWS and [AWS Lambda](https://aws.amazon.com/lambda/) were a good fit, the team first wanted to validate the stack. They did so by **building a new service entirely serverless** (greenfield project). They built it using PHP, Lumen, Bref, AWS Lambda, SQS, DynamoDB, SNS, S3, and KMS. That new service was the API exposed to Treezor's client to manage day-to-day bank operations. It was deployed to production at the end of 2020 and was a success. It was able to scale and handle the incoming traffic. To confirm that serverless worked well with other use cases, including the most complex ones, they did a second migration and **routed a part of live credit card transactions to the new serverless app**. If it worked with the live credit card traffic, it meant that the rest of the Treezor platform could run as serverless too. That migration was also a success and cleared the way for migrating more APIs. ## Lift-and-shift the legacy monolith on AWS Lambda The next step was to migrate the legacy PHP monolith from servers to AWS Lambda. Doing [a complete rewrite was unrealistic](https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/). It would have meant completely halting all other developments, investing months (or even years) into the new system, and crossing fingers for the rewrite to actually be a success. Instead, the team used Bref's [PHP-FPM runtime](https://bref.sh/docs/runtimes/fpm-runtime). This AWS Lambda runtime runs PHP "as usual", like on any server, using PHP-FPM. It allowed taking the monolithic codebase and running it as an HTTP application on AWS Lambda. To do a controlled migration, the team deployed the application both to the servers and to AWS Lambda. **Only 10 lines of code needed to be changed** to run the monolith on AWS Lambda. Using API Gateway, they were able to **route some of the API traffic to AWS Lambda and the rest to the old stack running on servers**. In case of any issue, it was possible to roll back the endpoint to the server stack. ![](https://bref.sh/docs/case-studies/treezor/treezor-1.svg) In October 2021, they successfully migrated their first API route to AWS Lambda, the most critical one handling all live credit card transactions. Over the following year, more and more API endpoints were migrated away from the servers to AWS Lambda. The migration of other API endpoints started in March 2022. Over the following year, the team migrated all API endpoints, cron tasks, and batch scripts to AWS Lambda. The servers were finally shut down in March 2023. The entire migration took 1 year of planning and design, and 1 year of implementation. The main challenge was dealing with cron tasks running for more than 15 minutes (the maximum execution time on AWS Lambda). They needed to be split into smaller tasks. The Bref runtime also needed some customizations, for example to run custom PHP versions. **The migration was a complete success**, and this is reflected in the key metrics tracked for the migration. With the new serverless stack: - API response times were **2.5 times faster**. - On-call alerts were **divided by 2** or even 3 times. - API endpoint timeouts for card transactions were **reduced by a factor of 10**. ## Refactoring to serverless microservices Now that the infrastructure was running smoothly, the team turned to the code itself. The goal: turning the legacy PHP monolith into a maintainable system. Headcount in the Treezor IT department was growing and having all teams work on a single monolith was painful. The target architecture was set: a collection of **domain-oriented microservices**. The use of API Gateway routing turned useful here too: it allowed applying the "strangler" pattern. As each domain was spun out into a separate service, the API Gateway routes could be transparently updated to point to the new services. ![](https://bref.sh/docs/case-studies/treezor/treezor-2.svg) The migration to microservices is still an ongoing work. As of today, the stack is **90% serverless on AWS** and deployed with Terraform. Some new services are implemented in Go, but a majority are using PHP with Bref. API Gateway is used for [HTTP APIs](https://bref.sh/docs/use-cases/http), and EventBridge is used for [asynchronous communication between services](https://bref.sh/docs/use-cases/eventbridge). Other AWS services are used inside services to handle specific use cases, for example [job queues with SQS](https://bref.sh/docs/use-cases/sqs), DynamoDB databases, SNS for parallelizing tasks, or even Kinesis for data pipelines. ## Conclusion I find Treezor's story fascinating because it illustrates two very different use cases: - Being able to **lift and shift** existing PHP applications to AWS Lambda with very few changes. - And later going "all-in" on **event-driven microservices** and taking full advantage of what AWS has to offer. It shows that both options are valid and have their own benefits. First, **using AWS Lambda as a scalable PHP hosting platform works well**. For those who want scalability and simplicity, it is possible to avoid vendor lock-in and use AWS Lambda like any other hosting platform. Second, AWS Lambda has great integrations with other AWS services. That allows building **event-driven microservices by composing the best AWS services for the use case**: SQS for infinitely scalable queues, EventBridge for asynchronous communication between services, DynamoDB for very optimized data storage, API Gateway for out-of-the-box caching, security and advanced routing for APIs... And much more of course. Treezor's story also shows that the first option can be a good stepping stone to the second, removing some of the risk of a cloud migration. Thank you [Treezor](https://www.treezor.com/), and thank you [Nicolas](https://www.linkedin.com/in/nicolasbordes/) and [Julien](https://www.linkedin.com/in/julien-mortuaire-29126528/) for sharing that story with us! And if you want to work with Bref every day, [Treezor is hiring](https://www.welcometothejungle.com/fr/companies/treezor) 😉 --- Source: https://bref.sh/docs/upgrading/v2 # Upgrading to Bref 2.0 ## Updating dependencies ### PHP 8 required Bref 2.0 now requires PHP 8.0 or greater. ### Composer Dependencies You should update the `bref/bref` dependency in your application's composer.json file: ```diff - "bref/bref": "^1.0", + "bref/bref": "^2.0", ``` Then run `composer update bref/bref --with-all-dependencies`. If you use the [Bref Extra extensions](https://github.com/brefphp/extra-php-extensions), you also need to update the `bref/extra-php-extensions` package to version `^1.0`. ### Serverless Framework If you are using Bref with [Serverless Framework](https://www.serverless.com/framework) (which is the default), Bref 2.0 requires Serverless Framework v3. Serverless Framework 2.x is no longer supported. To check your Serverless Framework version, run: ```bash serverless --version ``` If you need to upgrade, [read the Serverless Framework documentation](https://www.serverless.com/framework/docs/getting-started#upgrade) (short version: run `npm install -g serverless@3`). ## PHP runtimes There is a new (simpler) syntax to use Bref's PHP runtimes in `serverless.yml`: ```yaml functions: hello: # ... runtime: php-84 # instead of: runtime: provided.al2 layers: - ${bref:layer.php-84} ``` The [bref.sh](https://bref.sh) documentation now uses the simpler `runtime: php-84` syntax, but `${bref:layer.php-xxx}` variables still work! These variables are not deprecated. There are no breaking changes here. ## Bref CLI The following commands of `vendor/bin/bref` have changed: - `vendor/bin/bref cli` is replaced by the simpler `serverless bref:cli`. For example: ```bash vendor/bin/bref cli mystack-dev-artisan --region=eu-west-1 -- migrate --force # becomes: serverless bref:cli --args="migrate --force" ``` No need to provide the function name or the region anymore. Read [the Console documentation](https://bref.sh/docs/runtimes/console#usage) to learn more. You will also find alternatives if you don't use the `serverless` CLI. - `vendor/bin/bref local` is replaced by the simpler `serverless bref:local`. For example: ```bash vendor/bin/bref local --handler=my-handler.php # becomes: serverless bref:local -f hello ``` No need to provide the handler file name anymore, we directly use the function name. The new `serverless bref:local` command has similar arguments as `serverless invoke`. Read [the Local Development documentation](https://bref.sh/docs/local-development/event-driven-functions) to learn more. You will also find alternatives if you don't use the `serverless` CLI. - `vendor/bin/bref layers` is replaced by the simpler `serverless bref:layers`. Layer versions are also available at [runtimes.bref.sh](https://runtimes.bref.sh/) if you don't use the `serverless` CLI. These changes allowed us to simplify the commands (automatically use the AWS region, credentials and stage from the `serverless` CLI). It also allowed us to remove the biggest `bref/bref` Composer dependencies and make the package much lighter. ## Development Docker images The development Docker images have been simplified. You will need to update your `docker-compose.yml`. Before (Bref v1): ```yaml services: web: image: bref/fpm-dev-gateway ports: - '8000:80' volumes: - .:/var/task depends_on: - php environment: HANDLER: public/index.php DOCUMENT_ROOT: public app: image: bref/php-80-fpm-dev volumes: - .:/var/task console: image: bref/php-80 volumes: - .:/var/task entrypoint: php ``` After (Bref v2): ```yaml services: app: image: bref/php-80-fpm-dev:2 ports: [ '8000:8000' ] volumes: - .:/var/task environment: HANDLER: public/index.php DOCUMENT_ROOT: public ``` The `bref/php-XX-fpm-dev` images can now run HTTP applications, console commands as well as event-driven functions too. Read more in [web app local development](https://bref.sh/docs/local-development). The `bref/fpm-dev-gateway` image is no longer needed, and code running in `bref/php-XX-fpm-dev` now runs in an environment even closer to production. > Note: In order to be future-proof, we recommend to set the Bref major version in the Docker images. For example, with Bref 2, use `bref/php-80-fpm-dev:2` instead of just `bref/php-80-fpm-dev`. ## Smaller breaking changes that might impact you The changes below should not impact the majority of users. However, if you are using any of these features, you might need to update your code. ### Removed `separateVendor: true` The `separateVendor` option (in `serverless.yml`) has been removed. It allowed to deploy the `vendor/` directory separately from the app to reduce the size of the deployment package. It was very buggy (contributed a long time ago and not maintained by anyone) and added a lot of technical debt. Instead, when hitting the 250MB limit, you are encouraged to [deploy via Docker images](https://bref.sh/docs/web-apps/docker.html) instead (which have a limit of 10GB). ### Removed `Bref\Lambda\SimpleLambdaClient` The `Bref\Lambda\SimpleLambdaClient` class has been removed. It was a simple wrapper around the AWS SDK that was used internally in Bref. If you were using this class in your application, you can use the AWS SDK directly, or the simpler and lighter [Async AWS package](https://async-aws.com/clients/lambda.html). ### Removed `Bref\Websocket\SimpleWebsocketClient` The `Bref\Websocket\SimpleWebsocketClient` class has been extracted to a separate package: [bref/api-gateway-websocket-client](https://github.com/brefphp/api-gateway-websocket-client). ### Changed the AWS account ID for AWS Lambda layers The AWS account ID where Bref layers are published is different for v2. That lets us keep releasing Bref v1 layers without mixing up layer numbers (e.g. layer 24 being a v2 layer and layer 25 being a v1 layer). For Bref v2 layers, you need to use `534081306603` as the AWS account number (instead of `209497400698`). ```bash # Bref v1 layer arn:aws:lambda:eu-west-2:209497400698:layer:php-82-fpm:xxx # Bref v2 layer arn:aws:lambda:eu-west-2:534081306603:layer:php-82-fpm:xxx ``` --- Source: https://bref.sh/docs/upgrading/v3 # Upgrading to Bref 3.0 Read the [Bref 3.0 release announcement](https://bref.sh/news/03-bref-3.0) to learn about all the new features and improvements. ## Updating dependencies ### PHP 8.2 required Bref 3.0 now requires PHP 8.2 or greater. ### Composer Dependencies Update all Bref packages in your `composer.json` file from `^2` to `^3`, for example: ```json filename="composer.json" "require": { "bref/bref": "^3", "bref/laravel-bridge": "^3", // if you use Laravel "bref/symfony-bridge": "^3", // if you use Symfony "bref/extra-php-extensions": "^3" // if you use extra extensions } ``` > [!TIP] > > Only update the versions for the packages you actually have in your project. > [!TIP] > > If you use [`bref/extra-php-extensions`](https://github.com/brefphp/extra-php-extensions), note that it jumps from v1 to v3 (there is no v2). The v1 version was used with Bref v2, and v3 is aligned with Bref v3. Then run: ```bash composer update --with-all-dependencies ``` ## PHP extension changes The following improvements in Bref 3.0 let you clean up old configurations. ### PostgreSQL extension is enabled by default The PostgreSQL PDO extension (`pdo_pgsql`) is now enabled by default in Bref layers. If you had enabled it manually via a `php.ini` file, you can remove that line: ```diff filename="php/conf.d/php.ini" -extension=pdo_pgsql ``` ### Redis extension is now built-in The Redis PHP extension is now included in Bref layers by default. If you were using the Redis extension from [bref/extra-php-extensions](https://github.com/brefphp/extra-php-extensions), remove the layer from your `serverless.yml`: ```diff filename="serverless.yml" functions: api: # ... layers: - - ${bref-extra:redis-php-84} ``` And enable the extension via a `php.ini` file in your project (`php/conf.d/php.ini`): ```diff filename="php/conf.d/php.ini" extension=redis ``` If Redis was the only `bref/extra-php-extensions` extension you were using, you can uninstall the package: ```bash composer remove bref/extra-php-extensions ``` ## Container image changes If you deploy using [container images](https://bref.sh/docs/deploy/docker), you must update your `Dockerfile`. **If you don't have a `Dockerfile` in your project, you can skip this section.** Since Bref container images have been merged into a single `bref/php-xx` image, the following images don't exist anymore: `bref/php-xx-fpm` and `bref/php-xx-console`. ### Option 1: Set BREF_RUNTIME in the Dockerfile The simplest upgrade path is to update your Dockerfile to use the unified image and set the runtime: ```diff filename="Dockerfile" - FROM bref/php-84-fpm:2 + FROM bref/php-84:3 + ENV BREF_RUNTIME=fpm # ... ``` Replace `fpm` with `function` or `console` depending on your use case. ### Option 2: Use one image for all function types (recommended) A major benefit of v3 is that you can now use **a single Docker image** for all your functions (web, console, queues, etc.) and set `BREF_RUNTIME` per function in `serverless.yml`. **Dockerfile** (single image for everything): ```dockerfile filename="Dockerfile" FROM bref/php-84:3 # Your application code COPY . /var/task # No BREF_RUNTIME set here! ``` **serverless.yml** (set runtime per function): ```yml filename="serverless.yml" functions: web: image: name: my-app-image environment: BREF_RUNTIME: fpm # Web/HTTP function events: - httpApi: '*' console: image: name: my-app-image environment: BREF_RUNTIME: console # Console commands worker: image: name: my-app-image environment: BREF_RUNTIME: function # Queue worker events: - sqs: arn: !GetAtt MyQueue.Arn ``` This approach lets you build and deploy a single Docker image for all function types, simplifying your deployment pipeline. ### Local development images If you use the `bref/php-84-fpm-dev` image for local development, update it to: ```diff filename="docker-compose.yml" - FROM bref/php-84-fpm-dev:2 + FROM bref/php-84-dev:3 ``` You can then set `BREF_RUNTIME` environment variable in your `docker-compose.yml` file or in the `Dockerfile` directly. ## Smaller breaking changes that might impact you The changes below should not impact the majority of users. However, if you are using any of these features, you might need to update your code. ### Changed the AWS account ID for AWS Lambda layers The AWS account ID where Bref layers are published is different for v3. That lets us keep releasing Bref v2 layers without mixing up layer numbers. If you reference the layers via their full ARN, you must update the Bref AWS account number to `873528684822` (instead of `534081306603`). ```bash # Bref v2 layer arn:aws:lambda:us-east-1:534081306603:layer:php-84:xxx # Bref v3 layer arn:aws:lambda:us-east-1:873528684822:layer:php-84:xxx ``` **If you don't know what that means**, you're likely not concerned by this change. If you're not sure, search for `534081306603` in your codebase and replace it with the new account ID. ### AWS Lambda layers have been merged into a single layer If you configure the `runtime` in your functions using the following syntax: ```yml filename="serverless.yml" functions: web: # ... runtime: php-84-fpm # or `php-xx` or `php-xx-console` ``` ✅ **you have nothing to do**, your configuration is valid for Bref v3. However, if you specify AWS Lambda layers explicitly in `serverless.yml` (or through any other deployment method), for example: ```yml filename="serverless.yml" functions: web: # ... runtime: provided.al2 layers: - ${bref:layer.php-84} # or: - ${bref:layer.php-84-fpm} # or: layers: - 'arn:aws:lambda:us-east-1:534081306603:layer:php-84:21' ``` Then you must update your configuration. Under the hood, **all layers have been merged into one**, i.e. `php-xx-fpm` and `php-xx-console` have been merged into `php-xx` to make Bref layers simpler. The runtime is now defined via an environment variable that is automatically injected by Bref when using the `runtime:` syntax. - **Option 1**: switch to using the simpler `runtime:` syntax. Before: ```yml filename="serverless.yml" functions: web: # ... runtime: provided.al2 layers: - ${bref:layer.php-84} # or: - ${bref:layer.php-84-fpm} # or: - ${bref:layer.php-84-console} ``` After: ```yml filename="serverless.yml" functions: web: # ... runtime: php-84 # or: runtime: php-84-fpm # or: runtime: php-84-console ``` The examples above assume you are using PHP 8.4 (`php-84`) but you can replace `84` with another PHP version. If you include additional layers, you can keep them without issues, for example: ```yml filename="serverless.yml" functions: web: # ... runtime: php-84-fpm layers: - ${bref-extra:imagick-php-84} ``` - **Option 2**: change the layer names and define the `BREF_RUNTIME` environment variable. Before: ```yml filename="serverless.yml" functions: web: # ... runtime: provided.al2 layers: - ${bref:layer.php-84} # or: - ${bref:layer.php-84-fpm} # or: - ${bref:layer.php-84-console} ``` After: ```yml filename="serverless.yml" functions: web: # ... runtime: provided.al2 layers: - ${bref:layer.php-84} environment: # ... BREF_RUNTIME: function # for ${bref:layer.php-xx} # or BREF_RUNTIME: fpm # for ${bref:layer.php-xx-fpm} # or BREF_RUNTIME: console # ${bref:layer.php-xx-console} ``` The examples above assume you are using PHP 8.4 (`php-84`) but you can replace `84` with another PHP version. ### The `vendor/bin/bref` CLI has been removed The `vendor/bin/bref` CLI has been completely removed in Bref 3.0. This is a minor change since the CLI was already 90% removed in Bref 2.0 - only the `bref init` command remained for bootstrapping new projects. We now have better onboarding with improved documentation. Here are the alternatives: - **Scaffolding new projects**: Follow the [getting started guide](https://bref.sh/docs/setup) to create your `serverless.yml` manually. - **Layer versions**: Visit [runtimes.bref.sh](https://runtimes.bref.sh/) or run `serverless bref:layers`. - **Running console commands**: Use `serverless bref:cli` (unchanged from v2). - **Local development**: Use [Docker-based local development](https://bref.sh/docs/local-development). ### The hooks system has been removed The deprecated hooks system has been removed. This change affects very few users (less than 1%) as it was a low-level API used primarily by framework integrations. If you were using `Bref::beforeStartup()` or `Bref::beforeInvoke()`, you must migrate to the `BrefEventSubscriber` pattern: Before: ```php use Bref\Bref; Bref::beforeStartup(function () { // Setup code }); ``` After: ```php use Bref\Bref; use Bref\Listener\BrefEventSubscriber; class MyEventSubscriber extends BrefEventSubscriber { public function beforeStartup(): void { // Setup code } } Bref::events()->subscribe(new MyEventSubscriber()); ``` The `BrefEventSubscriber` class provides additional hooks: `afterStartup()`, `beforeInvoke()`, and `afterInvoke()`. This refactor powers better integrations like the [Laravel bridge](https://github.com/brefphp/laravel-bridge), [X-Ray integration](https://bref.sh/xray), and [Sentry integration](https://bref.sh/sentry). ### SOAP extension is now disabled by default The SOAP PHP extension is now disabled by default. It had very little usage, and disabling it helped reduce layer sizes to make room for more commonly used extensions. If you need the SOAP extension, you can enable it by creating a `php.ini` file in your project: ```ini filename="php/conf.d/soap.ini" extension=soap ``` ### Laravel version compatibility If you are using Laravel with Bref, note that: - **Laravel 10, 11, or 12 is required** (Laravel 8 and 9 are no longer supported). - Update `bref/laravel-bridge` to the latest version. The Laravel bridge has been updated to use the new `BrefEventSubscriber` pattern internally, but this change is transparent to users. ### CloudWatch log formatter enabled by default The [Bref Monolog formatter](https://github.com/brefphp/monolog-bridge) is now enabled by default in the Laravel and Symfony bridges. **This changes how your logs will look.** Logs now use a hybrid format that combines human-readable text with structured JSON: Before (plain text): ``` [2025-12-05 10:30:45] production.ERROR: Database connection failed ``` After (structured format): ``` ERROR Database connection failed {"message":"Database connection failed","level":"ERROR","context":{...}} ``` This format makes logs easier to read in CloudWatch and enables powerful filtering with CloudWatch Logs Insights (e.g., filter by log level or exception class). **Exception handling is greatly improved:** Previously, exception stack traces were split across multiple CloudWatch log records (one per line), making them difficult to read and browse. Now, the entire exception (including stack trace) is grouped in a single JSON object, making debugging much easier. If you prefer the old format, you can disable the formatter in your Laravel or Symfony configuration. See the [Bref Monolog documentation](https://github.com/brefphp/monolog-bridge) for details. ### Locale files from glibc langpacks are no longer included If you are using [`intl` / ICU](https://www.php.net/manual/en/book.intl.php) for locale-aware formatting (dates, numbers, currencies) or translations, you don't need to do anything as ICU is fully supported in Bref v3. **Modern frameworks (Laravel, Symfony…) use ICU, not `gettext`**. That means most users are not affected. If you are using PHP's native [`gettext`](https://www.php.net/manual/en/book.gettext.php) functions or rely on `setlocale()` with specific locales (e.g. for `strftime()`), glibc locale files are no longer included in Amazon Linux 2023 (AL2023). These will no longer work in Bref v3. You may either: - Switch to using `intl` / ICU, which is the modern standard for translations in PHP and is fully supported in Bref v3. - Or, if you want to keep using `gettext` or `setlocale()`, switch to [container deployments](https://bref.sh/docs/deploy/docker) and use a custom Docker image that includes the necessary locale files: ```dockerfile filename="Dockerfile" # Install the locale packages you need (e.g. English and French) RUN microdnf update -y && \ microdnf install -y glibc-langpack-en glibc-langpack-fr && \ microdnf clean all ``` --- That's it! Read the [Bref 3.0 release announcement](https://bref.sh/news/03-bref-3.0) to learn more about all the new features and improvements in this release. --- Source: https://bref.sh/docs/cloud # Bref Cloud documentation [Bref Cloud](https://bref.sh/cloud) is the service that complements the open-source Bref project. It deploys, monitors and operates serverless PHP applications on AWS Lambda, in your own AWS account, without the AWS complexity. To get started, [create a Bref Cloud account](https://bref.cloud/register). There is a free plan for personal projects, and paid plans come with a free trial. See the [pricing](https://bref.sh/cloud#pricing). } title="Learn more about Bref Cloud" href="https://bref.sh/cloud" /> } arrow={true} title="Get started with Bref Cloud" href="https://bref.cloud/register" /> ## What Bref Cloud does ### Deploy - **One command**: `bref deploy` from your machine or [from GitHub Actions](https://bref.sh/docs/cloud-deploy). No AWS credentials to create, distribute or rotate: Bref Cloud generates [short-lived credentials](https://bref.sh/docs/cloud-security) for every deployment. - **Databases and networks**: create MySQL or PostgreSQL databases (fixed instances, or serverless databases that scale and pause on their own) and private networks in a few clicks. - **Environments**: manage dev, staging and production across AWS accounts and regions from one dashboard, with the history of every deployment. ### Monitor - **Overview**: a diagram of each environment's architecture with live metrics on every component. - **Logs**: search logs or tail them in real time. - **Metrics**: Lambda invocations, concurrency, duration and errors, API Gateway, queues and dead-letter queues. - **Traces**: a trace explorer optimized for PHP applications, with a free [Bref X-Ray](https://bref.sh/xray) license included. See database queries, HTTP calls and AWS calls inside each invocation, and filter traces by route, job, command, cold start, or your own annotations, over up to 30 days. - **Performance**: the slowest database queries, the latency of every route, and the slowest jobs of your application, computed from the last 30 days of traces. - **Health checks**: verify that the database, the cache and the Lambda configuration of a deployed application are healthy (Laravel only for now). [Learn more about monitoring with Bref Cloud](https://bref.sh/docs/monitoring#bref-cloud). ### Operate - **Queues**: watch queues and dead-letter queues, and retry, delete or flush failed Laravel jobs. - **Commands**: run console commands (Artisan, Symfony console…) on a deployed environment from the dashboard. - **Secrets**: create and manage secrets for all your applications. - **Files**: browse, upload, rename and delete files in S3 buckets. ### Team and security - **Team members**: invite teammates with read-only, write or admin access. They never need AWS access. - **AWS access**: Bref Cloud connects to your AWS accounts through IAM roles and temporary credentials, never long-lived access keys. [Learn more](https://bref.sh/docs/cloud-security). --- Source: https://bref.sh/docs/cloud-getting-started # Getting started with Bref Cloud Bref Cloud is a service that makes it easy to deploy and monitor serverless PHP applications on AWS Lambda. Learn more about it [on the Bref Cloud homepage](https://bref.sh/cloud). To get started, [create a free Bref Cloud account](https://bref.cloud/register). You will be guided through the process of creating an AWS account (if needed) and connecting it to Bref Cloud. ## Installing the CLI To deploy PHP applications using Bref Cloud, you need to install the Bref CLI: ```shell composer global require bref/cli ``` The [`global` option](https://getcomposer.org/doc/03-cli.md#global) ensures that the `bref` CLI is installed globally on your machine in the `~/.composer/vendor/bin` directory. Make sure the command works by running: ```shell bref --version ``` If the `bref` command is not found, you need to add Composer's global bin directory to your `PATH`. Add this line to your shell configuration file (e.g. `~/.bashrc`, `~/.zshrc`, `~/.profile`): ```shell export PATH="$PATH:$HOME/.composer/vendor/bin" ``` If you want to find the location of the Composer's global installation directory, run `composer -n config --global home`. > [!TIP] > > To keep the `bref` CLI up-to-date, run `composer global update bref/cli`. ## Connecting the CLI To connect the CLI to your Bref Cloud account, run: ```shell bref login ``` This command will open a browser window where you can log in to your Bref Cloud account and authorize the CLI to access your account. When running in a non-interactive environment (e.g. CI/CD, or a Docker container), you can set a `BREF_TOKEN` environment variable instead. You can create a token in the [Bref Cloud dashboard](https://bref.cloud): - either as a [personal token](https://bref.cloud/user/api-tokens), which makes the CLI act on your behalf, with your permissions - or as a "bot token", which represents a service or a CI/CD pipeline that is not tied to a specific user --- Source: https://bref.sh/docs/cloud-deploy // Path relative to the copy in the `website/` folder # Deployment Bref Cloud deploys applications using Bref's `serverless.yml` file. If you haven't gotten started with Bref, follow the guide corresponding to your framework: } title="Get started with Laravel" arrow="true" href="https://bref.sh/docs/laravel/getting-started" /> } title="Get started with Symfony" arrow="true" href="https://bref.sh/docs/symfony/getting-started" /> The main difference with Bref Cloud is that you don't deploy by running `serverless deploy`. Instead, you deploy by running: ```shell bref deploy ``` (see [the Getting Started guide](https://bref.sh/docs/cloud-getting-started) to install the `bref` CLI) The benefits of deploying using Bref Cloud: - **Credentials**: You don't need to create AWS credentials or configure the `serverless` CLI, Bref Cloud handles this for you ([learn more](https://bref.sh/docs/cloud-security)). - **Deployment monitoring**: Bref Cloud provides a dashboard to view deployments of all your applications across all AWS accounts, regions, and environments. - **No need to install `serverless`**: Bref Cloud transparently installs and runs the `serverless` CLI. - **Better UX**: the `bref` CLI has minimal output with verbose mode that can be enabled in real time. When you run `bref deploy`, Bref Cloud runs the `serverless deploy` command for you in the background. ## Differences between `bref` and `serverless` The `bref` CLI uses AWS credentials from AWS accounts connected in [Bref Cloud](https://bref.cloud/aws-accounts). That means that it will ignore AWS credentials configured on your machine. The `bref` CLI also uses the [open-source fork of Serverless Framework](https://github.com/oss-serverless/osls), which is a fork of Serverless Framework v3, the last free and open-source version. This fork is maintained by Bref and ensures that the CLI is maintained and stable in the long run. Finally, the `bref` CLI uses "**environments**" instead of "**stages**". This is just a vocabulary change. That means that you should use the `--env` option instead of the `--stage` option: ```shell bref deploy --env production # is equivalent to serverless deploy --stage production ``` ## Deploying with Bref Cloud To deploy an application using Bref Cloud, you need to add your Bref Cloud team to the `serverless.yml` file: ```yaml service: my-app provider: name: aws # ... # Add these lines: bref: team: my-bref-cloud-team ``` (you can find your team name [in Bref Cloud](https://bref.cloud/app/create)) Then, you can deploy using the `bref` CLI: ```shell bref deploy # or for a specific environment bref deploy --env production ``` ## Deploying from GitHub Actions You can also deploy your application using GitHub Actions. First, make sure you have set up the `bref.team` option in `serverless.yml` as shown in the section above. Then, create a `.github/workflows/deploy.yml` workflow. Here is an example workflow you can use as a starting point: ```yaml name: Deploy on: push: branches: [ main ] jobs: deploy: runs-on: ubuntu-latest timeout-minutes: 15 concurrency: deploy-prod # Avoid deploying concurrently environment: name: prod # Optionally set a URL for the environment # url: https://example.com steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - name: Cache NPM dependencies uses: actions/cache@v4 with: path: ~/.npm # npm cache files are stored in `~/.npm` key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.OS }}-node- # You can remove this step if you don't have a `package.json` file - run: npm ci - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.4' coverage: none # Install the Bref CLI as a global tool tools: bref/cli - uses: ramsey/composer-install@v3 with: composer-options: '--optimize-autoloader --no-dev' # You may need to build your frontend application here # - run: npm run build - run: bref deploy --env=prod env: BREF_TOKEN: ${{ secrets.BREF_TOKEN }} ``` > [!TIP] > > Depending on your framework, you may need to add extra steps to build your application. For example with Laravel or Symfony you may want to build assets before deploying. Finally, you need to add the `BREF_TOKEN` secret to your repository: - [Create a "bot token" in your Bref Cloud team](https://bref.cloud/) - Go to your repository on GitHub - Click on "Settings" > "Secrets and variables" > "Actions" > "New repository secret" - Set the name to `BREF_TOKEN` and the value to the token you received after creating the bot token ![GitHub Actions secret](https://bref.sh/docs/cloud/github-actions-token.png) That's it! Your application will be deployed automatically when you push to the main branch. On the first deployment, the application will appear in the Bref Cloud dashboard. --- Source: https://bref.sh/docs/cloud-security # Secure access to your AWS accounts Bref Cloud accesses your AWS accounts to deploy and manage resources, such as AWS Lambda, S3 buckets, etc. To do this, it interacts with AWS via its SDK and API. Instead of using long-lived AWS credentials (access keys), Bref Cloud uses IAM roles with temporary credentials following AWS best practices. This document explains how Bref Cloud accesses these AWS accounts securely in more detail. ## Introduction ```mermaid graph LR Users -->|web UI| cloud[https://bref.cloud]; bref[bref CLI] -->|API| cloud; cloud -->|AWS API| user[User's AWS Account]; ``` "Accessing an AWS account" means that Bref Cloud performs actions in the account, like creating a Lambda function or an S3 bucket. Practically, this is done via the AWS API using the AWS SDK. To use the AWS API, Bref Cloud needs **AWS credentials** that give access to the target account. Bref Cloud does not use long-lived AWS credentials (aka AWS access keys). Instead, it follows standard AWS best practices and uses IAM roles with [temporary credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#bp-workloads-use-roles). Note that all of this happens automatically and is easy to set up in the Bref Cloud web interface. ## How it works ```mermaid graph LR subgraph Bref AWS account direction LR cloud[bref.cloud] -->|AssumeRole| accessor[BrefCloudAccessor IAM role]; end subgraph User's AWS account direction LR accessor -->|AssumeRole| accessRole[BrefCloudAccess IAM role]; accessRole --> resources["AWS resources (Lambda, etc.)"]; end ``` When you connect an AWS account to Bref Cloud, Bref Cloud creates a `BrefCloudAccess` IAM role in your account (you can also create it manually if you prefer). This role: - gives permissions to access specific resources in your account (like Lambda functions) - can only be "assumed" by the `BrefCloudAccessor` role from the Bref Cloud AWS account (called a "trust policy"). When Bref Cloud needs to access your account, it: 1. assumes the `BrefCloudAccessor` role in its own AWS account 2. uses this new role to assume the `BrefCloudAccess` role in your AWS account 3. gets temporary credentials that have the permissions of the `BrefCloudAccess` role These credentials are valid for a short period of time. Bref Cloud uses them to perform the actions you requested (like retrieving logs). They are also passed to the `serverless deploy` command when deploying your application, so that the command uses these temporary credentials without you having to do anything. Bref Cloud does not store these credentials. They are only used for the duration of the operation. ### Why use IAM roles? IAM roles are the recommended way to grant access to AWS resources over long-lived credentials (like access keys). Access keys can be leaked and are hard to rotate. IAM roles can only be used by the Bref AWS account. Thanks to IAM roles, **Bref Cloud does not store AWS credentials**. You can also transparently review the permissions that Bref Cloud has in your account, adjust them (the IAM role lives in your account), or revoke access at any time by deleting the `BrefCloudAccess` role from your account. ### Why use a separate `BrefCloudAccessor` role? The `BrefCloudAccessor` role is an additional layer of security: instead of directly granting the PHP application (Bref Cloud) access to your account, it jumps through an intermediate role (`BrefCloudAccessor`). The reasons for this are: - We can very tightly restrict access to this role (principle of least privilege). For example Bref AWS account administrators (IAM users) do not have access to the `BrefCloudAccessor` role. - The PHP application is deployed often, but the `BrefCloudAccessor` role should almost never change. Separating those helps avoid accidental unwanted changes. ### What permissions does the `BrefCloudAccess` role have? By default, the `BrefCloudAccess` role has a set of permissions that allows Bref Cloud to deploy and manage your applications. For example, it can create AWS resources (like Lambda functions) via CloudFormation, read logs and metrics, etc. When you set up Bref Cloud, you can review these permissions in the IAM role that is created in your account. You can also customize the permissions of the `BrefCloudAccess` role if you want to restrict it further. Of course, all Bref Cloud features may not work correctly in some cases if you restrict the permissions too much. ### How does Bref Cloud know which role to assume? When you connect your AWS account to Bref Cloud, the ARN of the `BrefCloudAccess` role is stored in Bref Cloud. This is how Bref Cloud knows which role to assume in your account. ### A note about `ExternalId` Bref Cloud follows AWS best practices and uses the [`ExternalId` feature for cross-account access](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html). The `ExternalId` is a unique identifier that is passed by Bref Cloud when assuming the role. This identifier is configured when the role is created, and is checked by AWS when the role is assumed. External IDs are randomly generated *per Bref Cloud team* and are stored encrypted in the Bref Cloud database. This technique has the following benefits: - It ensures that only a specific Bref Cloud team can assume the role and access your account (protection against the "confused deputy" problem). - In case of an extreme security breach where the `BrefCloudAccessor` IAM role is compromised, the attacker would not be able to access customer AWS accounts (the `ExternalId` acts as an additional layer of security). - On top of that, the `ExternalId` being stored in the database means that the attacker would need to also compromise the database to get it, which is an additional layer of security. - On top of that, the `ExternalId` being stored encrypted means that the attacker would also need to compromise the encryption key to get it (stored separately from the code and the database), which is another additional layer of security. Note that nothing is foolproof, but having multiple layers of security helps mitigate risks. ## Self-hosted Bref Cloud Self-hosting means running Bref Cloud inside your own AWS account. This is useful for companies that have strict security requirements and do not want to give access to a third-party service to their AWS account. When you self-host Bref Cloud, cross-account access happens as usual but the big difference is that access happens from an AWS account that you own to another AWS account that you own. ```mermaid graph LR subgraph Your Organization direction LR subgraph "Your Bref Cloud AWS account" cloud[Self-hosted bref.cloud] -->|AssumeRole| accessor[BrefCloudAccessor IAM role]; end subgraph "Your Application AWS account" accessor -->|AssumeRole| accessRole[BrefCloudAccess IAM role]; accessRole --> resources["AWS resources"]; end end ``` ## Frequently Asked Questions ### Can I revoke Bref Cloud's access to my AWS account? Yes, you can revoke access at any time by deleting the `BrefCloudAccess` IAM role from your AWS account. ### How does Bref Cloud handle multi-account deployments? If you need to deploy to multiple AWS accounts, you can connect each account to Bref Cloud separately. Each account will have its own `BrefCloudAccess` IAM role with appropriate permissions. ### How often are temporary credentials refreshed? Temporary credentials are only used for the duration of specific operations and are not stored. They typically have a short lifespan (usually 15 minutes), or even just the duration of an HTTP request. ### Can I audit what actions Bref Cloud performs in my account? You can enable [AWS CloudTrail](https://aws.amazon.com/cloudtrail/) to log all actions made in your AWS account. This allows you to audit the actions performed by Bref Cloud. ### Can I customize the permissions granted to Bref Cloud? Yes, you can modify the permissions in the `BrefCloudAccess` IAM role to restrict what Bref Cloud can do in your AWS account. However, restricting permissions too much may prevent certain Bref Cloud features from working correctly. Feel free to reach out to Bref Cloud support if you need help with this. ### Does Bref Cloud comply with my organization's security requirements? Bref Cloud follows AWS security best practices, including the principle of least privilege, temporary credentials, and cross-account access controls. For organizations with specific compliance requirements (like SOC2, HIPAA, etc.), you may want to consider the self-hosted option for complete control over the infrastructure.