Serverless Symfony applications
This guide helps you run Symfony applications on AWS Lambda using Bref. These instructions are kept up-to-date to be compatible with the latest Symfony version.
Multiple demo applications are available on GitHub at github.com/brefphp/examples/Symfony.
Setup
First, follow the Installation guide to create an AWS account and install the necessary tools.
Next, in an existing Symfony project, install Bref and the Symfony Bridge package.
composer require bref/bref bref/symfony-bridge
If you are using Symfony Flex, it will automatically run the bref/symfony-bridge recipe which will perform the following tasks:
- Create a
serverless.yml
configuration file optimized for Symfony. - Add the
.serverless
folder to the.gitignore
file.
Otherwise, you can create the
serverless.yml
file manually at the root of the project. Take a look at the default configuration provided by the recipe.
You still have a few modifications to do on the application to make it compatible with AWS Lambda.
Since the filesystem is readonly 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
:
// src/Kernel.php
namespace App;
+ use Bref\SymfonyBridge\BrefKernel;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
- class Kernel extends BaseKernel
+ class Kernel extends BrefKernel
{
// ...
Deploy
The application is now ready to be deployed. Follow the deployment guide.
For better performance in production, warmup the Symfony cache before deploying:
php bin/console cache:warmup --env=prod
Console
As you may have noticed, we define a function of type "console" in serverless.yml
. That function is using the Console runtime,
which lets us run the Symfony Console on AWS Lambda.
To use it follow the "Console" guide.
Logs
By default, Symfony logs in stderr
. That is great because Bref automatically forwards stderr
to AWS CloudWatch.
However, if the application is using Monolog you need to configure it to log into stderr
as well:
# config/packages/prod/monolog.yaml
monolog:
handlers:
# ...
nested:
type: stream
path: php://stderr
Environment variables
Since Symfony 4, the production parameters are configured through environment variables. You can define them in serverless.yml
.
# serverless.yml
provider:
environment:
APP_ENV: prod
The secrets (e.g. database passwords) must however not be committed in this file.
To learn more about all this, read the environment variables documentation.
Trust API Gateway
When hosting your site on Lambda, API Gateway will act as a proxy between the client and your 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.
You should add the following lines to config/packages/framework.yaml
# config/packages/framework.yaml
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).
Be careful with these settings if your app will not be executed only in a Lambda environment.
You can get more details in the Symfony documentation.
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
in trusted_proxies
.
Assets
To deploy Symfony websites, assets need to be served from AWS S3. The easiest approach is to use the Server-side website construct of the Lift plugin.
This will deploy a Cloudfront distribution that will act as a proxy: it will serve static files directly from S3 and will forward everything else to Lambda. This is very close to how traditional web servers like Apache or Nginx work, which means your application doesn't need to change! For more details, see the official documentation.
First install the plugin
serverless plugin install -n serverless-lift
Then add this configuration to your serverless.yml
file.
...
service: symfony
provider:
...
plugins:
- ./vendor/bref/bref
- serverless-lift
functions:
...
constructs:
website:
type: server-side-website
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
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.
# config/packages/framework.yaml
- trusted_headers: [ 'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-port' ]
+ trusted_headers: [ 'x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-host' ]
Then, you can compile assets for production in the public
directory
php bin/console assets:install --env prod
# if using Webpack Encore, additionally run
yarn encore production
Now run serverless deploy
, Lift will automatically create the S3 bucket, a Cloudfront distribution and
upload all specified files and directories to the bucket.
If you are not using Flex, update the
serverless.yml
file to exclude assets from the deployment (see the recipe)
For more details, see the Websites section of this documentation and the official Lift documentation.
Assets in templates
For the above configuration to work, assets must be referenced in templates via the asset()
helper as recommended by Symfony:
- <img src="/images/logo.png">
+ <img src="{{ asset('images/logo.png') }}">
Symfony Messenger
It is possible to run Symfony Messenger workers on AWS Lambda.
A dedicated Bref package is available for this: bref/symfony-messenger.
Caching
As mentioned above the filesystem is readonly, so if you need a persistent cache it must be stored somewhere else (such as Redis, an RDBMS, or DynamoDB).
Using DynamoDB for cache
A Symfony bundle is available to use AWS DynamoDB as cache store: rikudou/psr6-dynamo-db-bundle
First install the bundle
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 to deploy your DynamoDB table using the Serverless Framework.
The kernel.terminate
Event
The kernel.terminate
event 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 Messenger component instead.