DEV Community


Headless Wordpress CMS on AWS

Randy Findley
Accomplished technical founder with 2 exits. Experienced CTO, software architect, full-stack developer, and founder.
・7 min read

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

Now that we all emphatically agree that headless CMS is the way to go. Let’s take a look at these awesome CloudFormation templates 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 or Cosmic JS? 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.


Our Wordpress instance is running as an Elastic Container Service using Docker.

First, we have the VPC, then an 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. We’re using RDS as our MySQL database.

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. This plugin allows us to store the Media in S3 and use CloudFront as the CDN. Take a look at the diagram below. We also use the same CDN to cache the Wordpress API…

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 -o /tmp/
RUN unzip /tmp/ -d /usr/src/wordpress/wp-content/plugins
RUN rm /tmp/

RUN curl -L -o /tmp/
RUN unzip /tmp/ -d /usr/src/wordpress/wp-content/plugins
RUN rm /tmp/

RUN curl -L -o /tmp/
RUN unzip /tmp/ -d /usr/src/wordpress/wp-content/plugins
RUN rm /tmp/

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.


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


Install the following prerequisites:


  • VPC
  • ECS
  • RDS
  • ECR
  • Wordpress


This creates the Amazon Virtual Private Cloud 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


This creates an Elastic Container Service 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


This creates a Relational Database Service 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


This creates an Elastic Container Registry 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


Before we can launch this cloudformation stack. We need to push our service image to ECR.

Push Image

cd wordpress/src
  • Registry Authentication
    • 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
    • docker tag headless-wp:<version> <account-id>.dkr.ecr.<region>
    • docker tag headless-wp:<version> <account-id>.dkr.ecr.<region><version>
    • docker push <account-id>.dkr.ecr.<region>

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.

  - Name: AWS_REGION
    Value: !Ref AWS::Region
    Value: !Ref AWS::AccountId
        !Sub "${RDSStack}-RDSClusterEndpoint"
        !Sub "${RDSStack}-DatabaseUsername"
        !Sub "${RDSStack}-DatabasePassword"
    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" } }}]}]
    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 plugin. First we tell it to use our Task Role, AWS::IAM::Role, 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!

Discussion (5)

teaglebuilt profile image
dillan teagle

I am in the process of trying to deploy a blog with wordpress and react. As of now I am using terraform for creating an ec2 instance and then an s3 instance for the react front end. Now, I am using docker-compose to launch the application locally.

Looking at your approach, what would you suggest? You are also planning on hosting your front end through s3, obviously reduce cost, but how will you map this to where you can retrieve your data from your api in your frontend/s3?

I am considering your approach, although I do not have enough experience to suggest anything different.

mte90 profile image
Daniele Scasciafratte

Now I am curious as example how you are using that WordPress instance and if you made a new interface for it.

PS: thanks for the tutorial :-)

rgfindl profile image
Randy Findley Author

I am not using it yet. When I do I plan to use a static site generator, like, to create the frontend website. A static S3/CloudFront website, like the the one in here

I think it would be cool to also use the WordPress webhook to trigger AWS CodePipeline & CodeBuild to regenerate the static site. So when I update the WordPress content the website will also be updated. Maybe I'll do another post with that.

hzburki profile image
Haseeb Burki

Have you found a way to use Wordpress webhooks to trigger AWS CodePipeline and CodeBuild ???

Thread Thread
jfgrissom profile image
Forem Open with the Forem app