Documentation
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.

💡

A demo application is available on GitHub at github.com/brefphp/examples (opens in a new tab).

Setup

First, follow the Setup guide to create an AWS account and install the necessary tools.

Next, in an existing Symfony project, install Bref and the Symfony Bridge package (opens in a new tab).

composer require bref/bref bref/symfony-bridge --update-with-dependencies

If you are using Symfony Flex (opens in a new tab), it will automatically run the bref/symfony-bridge recipe (opens in a new tab) 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 (opens in a new tab) 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
{
    // ...

Deployment

Let's deploy the application to AWS Lambda:

serverless 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 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:

# 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 "<?php return [];" > .env.local.php
 
serverless 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:

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.

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.

Website assets

Have a look at the Website guide 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, which lets us run the Symfony Console on AWS Lambda.

For example, to execute an bin/console command on Lambda, run the command below:

serverless bref:cli --args="<console command and options>"

For example:

serverless bref:cli --args="doctrine:migrations:migrate"

For more details follow the "Console" guide.

Logs

By default, Symfony logs to stderr. That is great because Bref automatically forwards stderr to AWS CloudWatch.

However, if your application is using Monolog, you need to configure it to log to stderr as well:

config/packages/prod/monolog.yaml
monolog:
  handlers:
    # ...
    nested:
      type: stream
      path: php://stderr

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:

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

You can get more details in the Symfony documentation (opens in a new tab).

💡

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 (opens in a new tab) in trusted_proxies.

The kernel.terminate event

The kernel.terminate event (opens in a new tab) 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 instead.