Guides

How To Run Craft CMS On Amazon Lambda

Introduction

Craft CMS is a LAMP stack CMS. All traffic is routed through a single index.php file running at the web root of a server. This index.php file in turn knows where to go to find Craft and boot up all of its components. Those components in turn handle stuff like getting data out of the database, using templates to deliver HTML to web browsers, enforcing user rules and business logic, making outside API connections, &c.

When we run Craft on Lambda we're going to trick Craft into thinking that it is running on a server as it normally does. The Lambda function is going to use a Lambda layer called Bref which provides the PHP runtime necessary for the index.php file to be processed. That index.php file is in turn going to find Craft in a mounted EFS volume. That's basically going to be our server file system in the cloud. Craft will boot up and it will make a database connection to our RDS database which is again, a highly scalable database floating around in the cloud.

In theory this architecture can scale almost infinitely. Amazon does not have an infinite number of servers and your client does not have an infinite amount of money to give to Amazon, hence the 'almost' part. Still, by default 1000 concurrent Lambda instances can be running on a given account. The EFS file system can scale and expand to as large as the max size you sign up for. The RDS Aurora Serverless database can expand and burst and scale beyond what your client sites may ever need. All the while, you or your devops friends are making 0 updates to server software, applying 0 security patches, worrying 0% about unexpected traffic spikes, etc.

Should you run PHP on Lambda? Well, not if you want to obey the intent of the designers of Lambda. Lambda is for little stuff, little repetitive computational tasks that no normal self respecting server wants to bother with. But since we're web developers and we like to get dumb ideas in our heads for stuff to build we are going to run PHP in Lambda and Craft CMS along with it.

Overview

This guide takes you through the steps of setting up and running Craft CMS on Amazon Lambda. The following are the broad strokes of the steps necessary to set up the system.

  • Set up security groups and such
  • Set up Serverless services so that you can push code to Lambda
  • Configure Bref on Serverless as your PHP runtime
  • Spin up an EC2 instance as your helper
  • Set up an instance of an RDS Aurora Serverless database
  • Set up an instance of AWS EFS (Elastic File System)
  • Set up an instance of Redis ElastiCache to handle Craft sessions and caching
  • Install Craft CMS on your EC2 instance and make sure it works correctly with EFS and RDS
  • Alter the index.php file running in Lambda to point to Craft sitting in your EFS instance.
  • Test and tune

Step 1

Security Groups

Lambda security group

  • Create a security group to control access for Lambda.
  • Create an inbound rule for your Lambda security group to allow incoming HTTPS traffic from anywhere. Set Type as HTTPS and Anywhere for Source.

EFS security group

  • Create a security group to control access for your EFS volume.
  • Create an inbound rule for EC2 to access your EFS security group. Set NFS for Type and set your EC2 security group for Source.
  • Create an inbound rule for Lambda to access your EFS security group. Set NFS for Type and set your Lambda security group for Source.

ElastiCache security group

  • Create a security group to control access to your Redis ElastiCache instance.
  • Create an inbound rule in the Redis security group for EC2. Set Custom TCP traffic to port 6379. Set your EC2 security group for Source.
  • Create an inbound rule in the Redis security group for Lambda. Set Custom TCP traffic to port 6379. Set your Lambda security group for Source.

RDS security group

  • Create a security group to control access to your RDS database instance.
  • Create an inbound rule for Lambda to access your RDS security group. Set MYSQL/Aurora for Type and set your Lambda security group for Source.

Step 2

Set up Serverless on your local machine You have to get your code up into AWS Lambda somehow. One of the most convenient ways is through the Serverless framework. This is the method recommended by Bref, the PHP Lambda layer we'll be using. Rely mainly on Bref for this: https://bref.sh/docs/installation.html

  • Install Serverless with npm install -g serverless
  • Create AWS access keys. Follow this guide.
  • Save those keys for the next step.

Step 3

Update your local aws config to provide AWS access keys for the various projects you'll be working on.

  • Open ~/.aws/credentials in an editor.
  • Follow this guide to update the keys in that file.
  • When ready to work on one of your projects, in your terminal, execute the command to tell Serverless which AWS profile to use for the project you're working on.

export AWS_PROFILE="profileName2"

Step 4

Initialize your project

Now that you have Serverless installed on your local machine and you have set up the credentials necessary for it to connect to your AWS account, you can initialize your project. The following steps come to us from https://bref.sh/docs/first-steps.html.

  • composer require bref/bref
  • vendor/bin/bref init
  • At the prompt choose [0] Web application
  • Make sure you get a success message like this:

initialize-bref

Step 5

Try a test deployment. Run the main serverless deploy command to see if you can get the sample Bref deployment up on a Lambda function. Let's get some proof of life.

sls deploy

sls-deploy

If you get a successful deployment you're Lambda function will be running. Serverless will provide you with the https endpoint you can hit in a browser to see the Lambda function fire. Something like this: https://9n50x1f26g.execute-api.us-east-1.amazonaws.com. Hit your url in a browser and you'll see Bref's welcome page.

Bref

Step 6

Find your Lambda function up in your AWS portal.

Availability Zone

  • Your new Lambda function was likely deployed to AZ us-east-1 as the default that Bref sets. You can manipulate this by opening serverless.yml which is in the root of the project directory you're in on your machine.
  • Find provider: region: us-east-1. Change this to whatever AZ you're in by default. I'm in California so mine is us-west-1.
  • Redeploy your Lambda function for the new AZ, if necessary: sls deploy

Step 7

Review your Lambda function in the AWS portal. Click on the name of your new Lambda function. You'll see something like this:

Lambda Brief

  • Click Layers on your function and you will see php-74-fpm as the one layer your Lambda function is using. Lambda has the ability to pull in extra code and runtimes as layers, 5 max, that help you build your Lambda functions with greater complexity.
  • Click API Gateway then click Details and you can see the details of your Lambda function. You'll see the endpoint url and other metadata.
  • While you're in the Configuration tab of the API Gateway, have a look at VPC. In future steps you will need to join your Lambda function to one of your VPC's so that the Lambda function is allowed to access the EFS volume and RDS database you will set up. Remember where you saw this and remember it with dread.

Step 8

Update the Lambda IAM role to attach extra policies

  • While looking at your Lambda function in AWS, click the Configuration tab.
  • Click Permissions
  • Under Execution Role click on the role that was setup by default by Serverless.
  • Click Attach policies
  • Find AWSLambdaVPCAccessExecutionRole and check the box.
  • Find AmazonElasticFileSystemClientFullAccess and check the box.
  • Find AmazonElastiCacheFullAccess and check the box.
  • Click Attach policy

Step 9

Set Lambda permissions, VPC and security groups.

  • Click the Configuration tab.
  • Choose VPC
  • Click Edit to attach your Lambda function to your VPC.
  • Choose the VPC you're using for this project, probably your default.
  • Choose some subnets.
  • Choose the security group you created specifically for Lambda. That group has the outbound rules to allow access to EFS and Redis.
  • Click Save

Step 10

Create an EFS volume

Craft is going to actually live in an EFS volume. It's a file system, like on your machine or on a normal server. This volume will be shared between your Lambda function and your EC2 helper instance. This way all that your Lambda function will contain is the Bref PHP layer and the main Craft CMS index.php file. We will manipulate that file so that it knows where to find Craft in your EFS volume.

  • Go to https://console.aws.amazon.com/efs.
  • Click Create file system
  • Give it a name and assign it to your default VPC. Leave the Availability at Regional.
  • Once the volume is available click the Access points tab.
  • Click Create access point and give it a sensible name.
  • Under Root directory path enter /
  • Under POSIX User ID enter 1000 that corresponds to the ec2-user
  • Under Group ID enter 1000
  • Under Root directory creation permissions enter 1000 for Owner User ID, 1000 for Owner Group ID and 0777 for POSIX permissions.
  • Click Create access point

Step 11

Step access permissions on your EFS volume

  • Find your EFS file system in the EFS
  • Click the Network tab
  • Click Manage
  • For each of the availability zones, assign the security group you created for EFS. You can remove the default security groups.
  • Click Save

Step 12

Grab the command for mounting your EFS instance

  • While looking at your new EFS instance, click Attach
  • Copy the command you find under Using the EFS mount helper. Something like this sudo mount -t efs -o tls fs-c900fed1:/ efs
  • Save this command for later.

Step 13

Set up an ElastiCache Redis instance. Craft on Lambda will need a place to store sessions.

  • Go to https://console.aws.amazon.com/elasticache
  • Click on Redis in the left nav.
  • Click Create
  • Give your Redis instance a suitable name and description.
  • Under Node type choose t2 > cache.t2.micro since this is not for production use.
  • Leave the other defaults in place.
  • Make sure the VPC ID matches the VPC you have put the other services in.
  • Choose subnets, assuming you have left Multiple Availability Zones on.
  • Under Subnet group choose Create new and give it a name and description.
  • Under Security click the pencil icon to edit the security group.
  • Choose the security group you created for Redis ElastiCache.
  • Click Create

Step 14

Spin up an EC2 instance. This EC2 instance will be the tool that helps you install Craft on your EFS volume, troubleshoot DB connection issues and generally just be a good friend.

Step 15

Mount the EFS volume from your EC2 instance.

  • SSH to your EC2 instance.
  • Create a directory that will serve as your mount directory for your EFS instance. sudo mkdir /mnt/efs
  • Remember the command you copied above under the Attach panel of your EFS instance? Use that command inside your EC2 instance. Note that the end of the command will point at the mount directory you created on your EC2 instance, ie '/mnt/efs'.
  • sudo mount -t efs -o tls fs-123456:/ /mnt/efs

Step 16

Move Craft into your EFS instance. This will allow your Lambda function access to it.

  • sudo mkdir /mnt/efs/craft
  • sudo chown ec2-user:ec2-user /mnt/efs/craft
  • sudo mv -f /var/www/craft/{.,}* /mnt/efs/craft

You might find that this step is fussy. You may get some error messages about failing to move folders and such. Just keep in mind that the end result is to get the entire contents of your craft folder into your mounted volume. You may have to fidget with it a little. I certainly did.

Step 17

Update Craft on EC2 to point to the EFS instance.

  • Open your index.php file. That's sitting in /var/www/html.
  • Edit the CRAFT_BASE_PATH value. This now needs to point to the mount directory for your EFS instance.
  • Something like: define('CRAFT_BASE_PATH', '/mnt/efs/craft');
  • Update your Craft config to serve the cpresources directory from EFS. This will help Lambda serve Craft in a future step.
  • Open /mnt/efs/craft/config/general.php.
  • Add this line to the existing config array: 'resourceBasePath' => '/mnt/efs/cpresources'

Step 18

Update Craft on EC2 to use the Redis instance you setup previously.

  • Open /mnt/efs/craft/composer.json and add this line to the require block: "yiisoft/yii2-redis": "~2.0.0" You can do this with nano while connected to your EC2 instance via SSH.
  • cd /mnt/efs/craft
  • Run this command: composer update
  • Open /mnt/efs/craft/config/app.php
  • Change that file so that it looks like the following, note the redis hostname is your Redis instance endpoint:
use craft\helpers\App;

return [
    'id' => App::env('APP_ID') ?: 'CraftCMS',
    'modules' => [
        'my-module' => \modules\Module::class,
    ],
    'components' => [
        'redis' => [
            'class' => yii\redis\Connection::class,
            'hostname' => 'serverless-craft.bzndnwe.ng.0001.use1.cache.amazonaws.com',
            'port' => 6379,
            'database' => 0,
            //'password' => App::env('REDIS_PASSWORD'),
        ],
        'session' => [
            'class' => yii\redis\Session::class,
            'as session' => [
                'class' => craft\behaviors\SessionBehavior::class,
            ],
        ],
        'cache' => [
            'class' => yii\redis\Cache::class,
            'defaultDuration' => 86400,
            'keyPrefix' => App::env('APP_ID') ?: 'CraftCMS',
        ],
    ],
];

Step 19

Update serverless.yml to make Lambda more compatible with Craft. This part is really intricate. AWS is really fussy with how it allows services to connect to one another, especially concerning Lambda. Be careful and patient here.

  • Open the composer.json file in your local Serverless project directory.
  • Add this line in the required block: "bref/extra-php-extensions": "^0.10.10"
  • Run composer update
  • Open the serverless.yml file in your local Serverless project directory.
  • Add this line in the plugins block under the ./vendor/bref/bref line: - ./vendor/bref/extra-php-extensions
  • Add this line in the layers block of your function definition, replacing us-east-1 with the AZ your Lambda function is running in: - arn:aws:lambda:us-east-1:403367587399:layer:gd-php-74:14
  • Go to https://console.aws.amazon.com/vpc
  • Under Security find Security Groups.
  • In the list of security groups, find the security group you created for your Lambda function. Grab that id. Something like: sg-075c0a333e51ad0ba
  • Find your default VPC in AWS. Click on it to view its details.
  • Click the route table listed under Main route table
  • Click the Subnet associations tab.
  • Grab the subnet ids listed and be ready to use them in your serverless.yml file.
  • Under provider in your serverless.yml file, add the block below. Replace the security group id and subnet ids as appropriate from what you found in the steps above. Also note the addition of support for binary file types. This tells Lambda that it may permit .png, .woff, .ttf and other binary file formats. The Craft control panel needs this support and so may your front end code.
    apiGateway:
        binaryMediaTypes:
            - '*/*'
    environment:
        BREF_BINARY_RESPONSES: '1'
    vpc:
        securityGroupIds:
            - sg-075c0a333e51ad0ba
        subnetIds:
            - subnet-056919950b6436c87
            - subnet-0252c099d59a863fb
  • Go to your EFS instance.
  • Click on the Access points tab.
  • Click on the access point in the list.
  • Find the Access point ARN. Something like: arn:aws:elasticfilesystem:us-east-1:9422218975528:access-point/fsap-0120183391950ab01
  • In your serverless.yml file after the layers block, add the following block to point Lambda at your EFS instance. Replace the ARN with what you found above.
        fileSystemConfig:
            localMountPath: /mnt/efsoops
            arn: arn:aws:elasticfilesystem:us-east-1:9422218975528:access-point/fsap-0120183391950ab01

Step 20

Grab the main index.php file from your EC2 instance running Craft. Replace the index.php file in your Serverless project directory with the index.php file from your EC2 instance.

  • Add this block at the top of the file below <?php:
ini_set('display_errors', 1);
error_reporting(E_ALL);

$_SERVER['DOCUMENT_ROOT']   = '/var/task';
  • Deploy your updated Lambda function: sls deploy

Step 21

Adjust folder permissions

Once your updated Lambda function has deployed via Serverless you should be able to hit it in a browser. Remember the endpoint looks something like this:

https://b3rmrwssw9li.execute-api.us-west-1.amazonaws.com/

You will likely see folder permission errors that need to be corrected. While logged in to your EC2 instance via SSH, issue these commands:

  • sudo mkdir /mnt/efs/cpresources
  • sudo chown ec2-user:apache cpresources
  • sudo chmod -R 0777 /mnt/efs/craft/storage

Step 22

Test and tune

There is likely still much to be done with your new Lambda Craft site. But hopefully this guide got you up and running with a decent start.

Additional Notes

CP Resources

As noted by @mattgrayisok, Craft serves a lot of control panel assets. They all live in the cpresources directory. With Lambda you're dealing with an ephemeral thingy. It spins up, does what it's told and dies a peaceful death. Nothing is stored. So Craft cannot serve cpresources from a normal directory. Instead, when Craft doesn't find a cpresource where it expects, it streams through PHP to the browser. This is slow. Matt has a solution for this involving a custom plugin for Craft. But I instead found it easier to front my Lambda function with CloudFront. I then made a rule to cache all cpresources at the edge for like 24 hours. This makes the Craft CP on Lambda very fast.