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

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

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.

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.

### 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)

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/).

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.

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.

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

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.