Creating Serverless Websites with AWS, Bref, and PHP
Tags: aws, bref, php, serverless
Published: 2021-11-20 02:29:18

## Prerequisites

I would like to start by explaining that I am going to assume you are familiar with [AWS](https://aws.amazon.com/) and the following topics. If that's not the case then I would recommend you at least try to understand the basics of these topics before attempting to follow along.

* [API Gateway](https://aws.amazon.com/api-gateway/)
* [CloudFront](https://aws.amazon.com/cloudfront/)
* [EC2](https://aws.amazon.com/ec2/)
* [ECR](https://aws.amazon.com/ecr/)
* [Lambda](https://aws.amazon.com/lambda/)
* [Route53](https://aws.amazon.com/route53/)
* [S3](https://aws.amazon.com/s3/)
* [Serverless](https://aws.amazon.com/serverless/)

____

## Introduction 

Serverless websites on AWS are a common theme. This is expected since hosting a serverless website can have many benefits over traditional web hosting solutions that involve virtual servers. Benefits like: better scalability, lower costs, and reduced latency. 
> https://dashbird.io/blog/business-benefits-of-serverless/

Most of these serverless websites rely on _static_ web technologies like JavaScript. This permits the simplest serverless website solution as pictured below.

![Static Website Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wn6cd5deldopayjxr8cm.png)

The problem we're aiming to solve is that this simplistic architecture does not support _dynamic_ web technologies like ASP.NET, JSP, or PHP that are still used to build incredible websites today. Websites like Etsy, Facebook, and Slack.
> https://trio.dev/blog/companies-using-php

Dynamic websites can leverage AWS to achieve serverless hosting. It just takes a couple of extra steps and a couple of extra services. Let's walk through a working solution.

____

## Bref 

In the scenario of PHP, there is an amazing project called [Bref](https://bref.sh/), which we can use to simplify our configuration. With Bref we can run our PHP websites on Lambda instead of relying on services like EC2. 

To quote their documentation,
> _Bref is an open-source project that brings full support for PHP and its frameworks to AWS Lambda._

### Usage

The simplest implementation is to package our website as a container image and eventually deploy it to Lambda. In order to reference an image for our Lambda function, we need to upload our image to ECR first. Luckily AWS makes interacting with ECR extremely easy. Let's walk through the steps.

1. Create Dockerfile

    ```Dockerfile
    # reference bref as the base image
    FROM bref/php-74-fpm

    # install composer
    RUN curl -s https://getcomposer.org/installer | php

    # require bref
    RUN php composer.phar require bref/bref

    # copy contents into expected directory
    COPY . /var/task

    # set handler to our index
    CMD _HANDLER=index.php /opt/bootstrap
    ```
    > https://docs.docker.com/engine/reference/builder/
    > https://github.com/wheelers-websites/CloudGuruChallenge_20.10/blob/master/php-api/Dockerfile

2. Authenticate to ECR

    ```bash
    aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
    ```
    > https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/get-login-password.html
    > https://docs.docker.com/engine/reference/commandline/login/

3. Build Your Docker Image

    ```bash
    docker build -t ${AWS_ECR_REPOSITORY} .
    ```
    > https://docs.docker.com/engine/reference/commandline/build/

4. Tag Your Docker Image

    ```bash
    docker tag "${AWS_ECR_REPOSITORY}:latest" "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${AWS_ECR_REPOSITORY}:latest"
    ```
    > https://docs.docker.com/engine/reference/commandline/tag/

5. Push Your Docker Image

    ```bash
    docker push "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${AWS_ECR_REPOSITORY}:latest"
    ```
    > https://docs.docker.com/engine/reference/commandline/push/
    
![ECR Commands](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ao8q0vskff4qr2skco2.jpg)
> _Find these push commands customized to your account in the ECR console_

____

## Architecture

Now that our container image is within ECR we are able to create a Lambda function that references it. 

![ECR Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dcmknq0huthlb5botjyx.PNG)![Lambda Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tpcjv91hqucss9tcs49c.jpg)

After that is done we need to use API Gateway to provide an endpoint for our function. 

![API Gateway Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vgg68d2xtendj7k1f116.PNG)

Then we can reference that endpoint as a CloudFront origin to serve our website. 

![CloudFront Origins](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pbewp7iyo0b0pmxwlzp3.PNG)![CloudFront Behaviors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/odje6tv09pkxbqz348f1.PNG)

Finally, we can optimize this architecture by uploading our static content to S3 and serving that content with a separate origin.

![PHP Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ozrhrut6r6pijg34rqr.png)

____

## CI/CD

As we all know that websites change regularly, I wanted to mention [Continuous Integration](https://aws.amazon.com/devops/continuous-integration/) and [Continuous Deployment](https://aws.amazon.com/devops/continuous-delivery/) briefly. I would implement a process to build and update the container image in ECR and on Lambda when code changes are pushed to a Git repository. This is crucial to keep your environment current with your Git repository while avoiding manual deployments. Manual deployments are error-prone and completely avoidable. The image creation and ECR updates can easily be done by repeating the steps outlined above when the source changes in a script. Following those steps with another like below we can update the Lambda function automatically. 

```bash
aws lambda update-function-code \
--function-name ${AWS_LAMBDA_FUNCTION} \
--image-uri "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${AWS_ECR_REPOSITORY}:latest" \
--region ${AWS_REGION}
```
> https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/update-function-code.html

If you're new to CI/CD then I would recommend these great resources to get started. 
* [AWS CodePipeline](https://aws.amazon.com/codepipeline/)
* [GitHub Actions](https://docs.github.com/en/actions)

____

## Conclusion

We started by discussing the problem of hosting dynamic websites using serverless technology. Then we discussed Bref and Docker, which both help in hosting a serverless version of PHP. Then we walked through the architecture required. We pushed our container image to ECR and deployed our PHP code as a Lambda function. Then we created an API Gateway endpoint to invoke our Lambda function over HTTP. Finally, we uploaded our static content to S3 and placed both origins behind a CloudFront distribution. To conclude I would like to reiterate that dynamic websites can enjoy the benefits of serverless and I wholeheartedly believe it's worth the effort.

Please find this live serverless PHP website that I developed as a living example.  
* https://wheelerrecommends.com/

If you liked this content maybe you would like to [Buy Me a Coffee](https://www.buymeacoffee.com/wheelersweb) or connect with me on [LinkedIn](https://www.linkedin.com/in/wheelers-websites/).