Headless Wordpress CMS on AWS
Tags: cms, aws, docker, wordpress
Published: 2018-11-04 17:26:56

---
title: Headless Wordpress CMS on AWS
published: true
description: Quickly spin up a headless Wordpress CMS on AWS using Docker
tags: cms, aws, docker, wordpress
---

## Quickly spin up a headless Wordpress CMS on AWS using Docker

Headless CMS is very popular at the moment. But what is a headless CMS and why should I start using one?

A headless CMS is a backend that is **decoupled** from its frontends. The backend is where the content is created and published. Whereas the frontends are where the content is displayed (web, mobile apps, set-top box, Alexa, etc…).

For example, a traditional CMS is a single website. The same website is used to add the content as it is to display the content. The backend and frontend are **coupled**.

A headless CMS is only used to created and publish content. That content is then available through an API. The website or mobile apps, for displaying the content, are separate.

But why is decoupled better? Here are some of the reasons:

 - Faster and more flexible content delivery than traditional CMS
 - Resiliency in the face of changes on the user interface side (future-proof)
 - Rapid design iterations
 - Enhanced security
 - Fewer publisher and developer dependencies
 - Simpler deployment

_*** Thanks for the list [Brightspot](https://www.brightspot.com/blog/decoupled-cms-and-headless-cms-platforms)_

Now that we all emphatically agree that headless CMS is the way to go. Let’s take a look at these awesome [CloudFormation templates](https://github.com/rgfindl/headless-wordpress) I created to help you spin up and manage your headless (Wordpress) CMS.

To be clear, this is a Wordpress installation running on AWS using Infrastructure as Code and Docker.

Wait… Why use Wordpress and not one of these cool new headless CMS services like [Contently](https://contently.com/) or [Cosmic JS](https://cosmicjs.com/)? Well, these services are really great but they cost a lot of money, and I usually like to run everything myself, if I can help it. And… Wordpress is really good at managing content.

But how can Wordpress be a headless CMS? Easy, it has an API.

The trick is making Wordpress stateless so that it can autoscale, and run on AWS, with zero-downtime deployments.

Ok, headless CMS is cool, Wordpress is pretty cool, let's continue.

[https://github.com/rgfindl/headless-wordpress](https://github.com/rgfindl/headless-wordpress)

	
#Architecture
Our Wordpress instance is running as an [Elastic Container Service](https://aws.amazon.com/ecs/) using Docker.

First, we have the [VPC](https://aws.amazon.com/vpc/), then an [ECS](https://aws.amazon.com/ecs/) cluster, then an EC2 instance, and finally an ECS service with tasks. Our Wordpress service is exposed to the world via an [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). We’re using [RDS](https://aws.amazon.com/rds/) as our MySQL database.

[![](https://github.com/rgfindl/headless-wordpress/raw/master/diagram1.png)](https://github.com/rgfindl/headless-wordpress/raw/master/diagram1.png)

Our Wordpress service is stateless, which means we can’t rely on the file system to store content, like Wordpress Media or Plugins. Every time an instance of our Wordpress service is spawned it will only have the files that are baked into our Docker image.

Let’s take a look at how we handle Wordpress Media first.

We use a plugin called [WP Offload Media](https://wordpress.org/plugins/amazon-s3-and-cloudfront/). This plugin allows us to store the Media in [S3](https://aws.amazon.com/s3/) and use [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. Take a look at the diagram below. We also use the same CDN to cache the Wordpress API…

[![](https://github.com/rgfindl/headless-wordpress/raw/master/diagram2.png)](https://github.com/rgfindl/headless-wordpress/raw/master/diagram2.png)

Now how do we handle the Plugins? (We can ignore Templates because this is headless 😃)

Remember when I talked about baking things into our Docker image? That’s it… We have to include the Plugins in our Docker image. Let’s take a look at that Dockerfile and go through it.

```
FROM wordpress:latest

# Install unzip
RUN apt-get update; \
    apt-get install -y --no-install-recommends unzip

# Install WP plugins
RUN curl -L https://downloads.wordpress.org/plugin/amazon-s3-and-cloudfront.2.0.zip -o /tmp/amazon-s3-and-cloudfront.2.0.zip
RUN unzip /tmp/amazon-s3-and-cloudfront.2.0.zip -d /usr/src/wordpress/wp-content/plugins
RUN rm /tmp/amazon-s3-and-cloudfront.2.0.zip

RUN curl -L https://downloads.wordpress.org/plugin/advanced-custom-fields.5.7.7.zip -o /tmp/advanced-custom-fields.5.7.7.zip
RUN unzip /tmp/advanced-custom-fields.5.7.7.zip -d /usr/src/wordpress/wp-content/plugins
RUN rm /tmp/advanced-custom-fields.5.7.7.zip

RUN curl -L https://downloads.wordpress.org/plugin/custom-post-type-ui.1.5.8.zip -o /tmp/custom-post-type-ui.1.5.8.zip
RUN unzip /tmp/custom-post-type-ui.1.5.8.zip -d /usr/src/wordpress/wp-content/plugins
RUN rm /tmp/custom-post-type-ui.1.5.8.zip
```

As you can see our Dockerfile is really simple. It extends the latest Wordpress image and then installs 3 Plugins. It downloads, unzips, and copies each plugin to the wordpress/wp-content/ directory. When you launch your Wordpress site for the first time you’ll have to activate these plugins. The activation status is stored in MySQL so you won’t have to do it every time your ECS tasks recycle.

#Installation
Alright, let’s get this architecture installed. First a few prerequisites.

##Prerequisites
Install the following prerequisites:


- [AWS Account](https://aws.amazon.com/)
- [EC2 Key Pair](https://console.aws.amazon.com/ec2/v2/home)
- cim - (`npm install -g cim`)
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/installing.html) 

##Stacks
 - VPC
 - ECS
 - RDS
 - ECR
 - Wordpress

### VPC
This creates the [Amazon Virtual Private Cloud](https://aws.amazon.com/vpc/) that our ECS cluster and RDS database will run in.  
> Amazon Virtual Private Cloud (Amazon VPC) lets you provision a logically isolated section of the AWS Cloud where you can launch AWS resources in a virtual network that you define. 
```
cd vpc
cim stack-up
```

### ECS
This creates an [Elastic Container Service](https://aws.amazon.com/ecs/) that our EC2's will run in.
> Amazon Elastic Container Service (Amazon ECS) is a highly scalable, high-performance container orchestration service that supports Docker containers and allows you to easily run and scale containerized applications on AWS.
```
cd vpc
cim stack-up
```

### RDS
This creates a [Relational Database Service](https://aws.amazon.com/rds/) database cluster that our Wordpress application will use.
> Amazon Relational Database Service (Amazon RDS) makes it easy to set up, operate, and scale a relational database in the cloud.
```
cd rds
export DatabaseUsername="???"; export DatabasePassword="???"; cim stack-up
```

### ECR
This creates an [Elastic Container Registry](https://aws.amazon.com/ecr/) that will hold the docker images of our wordpress service.
> Amazon Elastic Container Registry (ECR) is a fully-managed Docker container registry that makes it easy for developers to store, manage, and deploy Docker container images.
```
cd ecr
cim stack-up
```

### Wordpress
Before we can launch this cloudformation stack.  We need to push our service image to ECR.
#### Push Image
```
cd wordpress/src
```
- [Registry Authentication](http://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth)
  - `aws ecr get-login --registry-ids <account-id>`
  - copy/past output to perform docker login,  also append `/headless-wp` to the repository url.
- Build Image
  - `docker build -t headless-wp:<version> .`
- [Push Image](http://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html)
  - `docker tag headless-wp:<version> <account-id>.dkr.ecr.<region>.amazonaws.com/headless-wp:latest`
  - `docker tag headless-wp:<version> <account-id>.dkr.ecr.<region>.amazonaws.com/headless-wp:<version>`
  - `docker push <account-id>.dkr.ecr.<region>.amazonaws.com/headless-wp`

#### Update Version
Make sure the `Version` parameter, in _cim.yml, matches the `version` tag from above.  The ECS Task Definition will pull the image from ECR.

#### Stack up
Once the `Version` is set you can use `cim stack-up` to update the stack with the new version.

```
cd wordpress
cim stack-up
```

Congratulations, your new Wordpress site is now available.  

First run through the Wordpress setup wizard.

Next enable some of the plugins we added.

Add a few blog posts and pages.

Then check out the API. Ex: `https://<cdn-url>/wp-json/wp/v2/posts`

#Environment Variables
Congratulations on getting your Headless Wordpress installed. If you got stuck anywhere along the way please don’t hesitate to reach out to me for help.

One thing I want to explain is the Wordpress environment variables because they really tie everything together. They tell our Wordpress installation about the RDS database, the Media S3 bucket, and CloudFront CDN URL. Let’s take a look. These can be found in the Wordpress stack’s [AWS::ECS::TaskDefinition](https://github.com/rgfindl/headless-wordpress/blob/master/wordpress/wp.stack.yml#L233).

```
Environment:
  - Name: AWS_REGION
    Value: !Ref AWS::Region
  - Name: AWS_ACCOUNT_ID
    Value: !Ref AWS::AccountId
  - Name: WORDPRESS_DB_HOST
    Value:
      Fn::ImportValue:
        !Sub "${RDSStack}-RDSClusterEndpoint"
  - Name: WORDPRESS_DB_USER
    Value:
      Fn::ImportValue:
        !Sub "${RDSStack}-DatabaseUsername"
  - Name: WORDPRESS_DB_PASSWORD
    Value:
      Fn::ImportValue:
        !Sub "${RDSStack}-DatabasePassword"
  - Name: WORDPRESS_CONFIG_EXTRA
    Value: !Sub
      - |
        define( 'AS3CF_AWS_USE_EC2_IAM_ROLE', true );
        define( 'AS3CF_SETTINGS', serialize( array(
          'bucket' => '${MediaBucket}',
          'copy-to-s3' => true,
          'serve-from-s3' => true,
          'domain' => 'cloudfront',
          'cloudfront' => '${DomainName}',
          'enable-object-prefix' => true,
          'object-prefix' => 'wp-content/uploads/',
          'force-https' => ${ForceHttps},
          'remove-local-file' => true
        ) ) );
        define( 'WP_HOME', '${CMSUrl}' );
        define( 'WP_SITEURL', '${CMSUrl}' );
      - {
        MediaBucket: !Ref MediaBucket,
        DomainName: !If [UseCustomDomain, !Ref Domain, !GetAtt CDN.DomainName],
        ForceHttps: !If [UseCustomDomain, 'true', 'false'],
        CMSUrl: !If [UseCustomDomain, { "Fn::ImportValue" : {"Fn::Sub": "${ECSStack}-CustomDomainUrl" } }, {"Fn::Sub": ["http://${Url}", {"Url": { "Fn::ImportValue" : {"Fn::Sub": "${ECSStack}-LoadBalancerUrl" } }}]}]
        }
  - Name: WORDPRESS_DEBUG
    Value: '1'
```

The WORDPRESS_DB_* variables are straight from the RDS stack. CloudFormation allows you to Export Output variables which can be imported in other stacks.

The WORDPRESS_CONFIG_EXTRA variable is where we configure the [WP Offload Media](https://wordpress.org/plugins/amazon-s3-and-cloudfront/) plugin. First we tell it to use our Task Role, [AWS::IAM::Role](https://github.com/rgfindl/headless-wordpress/blob/master/wordpress/wp.stack.yml#L208), via the AS3CF_AWS_USE_EC2_IAM_ROLE var. Then we the AS3CF_SETTINGS var to setup the plugin.

Thanks for reading. I hope you enjoyed it!