Headless CMS with Gatsby on AWS for $0.00 per month
Tags: aws, serverless, cms, gatsby
Published: 2021-10-19 15:19:02

---
title: Headless CMS with Gatsby on AWS for $0.00 per month
published: true
canonical_url: https://betterdev.blog/gatsby-website-with-headless-cms-on-aws/
description: With headless Prismic CMS, Gatsby, and a simple build pipeline, I got a website with CMS hosted on AWS at no cost.
tags: aws,serverless,cms,gatsby
cover_image: https://betterdev.blog/app/uploads/2021/10/2021-10-12-aws-headless-cms.png
---

Can you have a **website with a CMS on AWS** and not pay just for its existence? I looked at Amazon Lightsail, headless WordPress, and Webiny CMS but **found none of those suitable**. So I choose Prismic – a SaaS headless CMS, and Gatsby to create the site. Yes, I needed to make a pipeline to build my website after content changes. But when I did it, **I got a website with CMS hosted at no cost**.

## In Need of a Website
I build serverless applications on AWS daily. But when I needed to create a simple website for my new project, [SiteClue](https://siteclue.app), I surprisingly had no solution ready off the top of my head.

My requirements were simple:

- landing page with a **custom HTML**
- ability to **edit content with CMS**
- **hosted on AWS** – to have everything in one place
- **low to no fixed costs** – paying for hosting a website that (for now) has almost no visits is simply against my rules

## Regular, Headless or Serverless CMS on AWS
There are multiple ways to host a website with CMS on AWS. So why not…?

### Why not Amazon Lightsail with WordPress
If you look for **hosting a WordPress site on AWS**, the default recommended service is [Lightsail](https://aws.amazon.com/lightsail/). It’s an out-of-the-box solution for simple web applications. While being much less complex than EC2, you still get access to a virtual machine to install your app.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j428hthiewmorqcz1rz0.png" alt="Apache with WordPress and MySQL can be hosted on a single Amazon Lightsail instance.">
  <figcaption>Hosting WordPress on Lightsail can be as simple as that ([source](https://aws.amazon.com/blogs/compute/deploying-a-highly-available-wordpress-site-on-amazon-lightsail-part-1-implementing-a-highly-available-lightsail-database-with-wordpress/))</figcaption>
</figure>

The problem is, Lightsail pricing starts from $3.50 per month. That’s about twice what I pay for hosting this blog. And I don’t feel like paying three bucks for hosting a website that I edit once or twice per month just for the fact it exists. Serverless has spoiled me.
 
### Why not Headless WordPress on AWS
My second thought: hey, there was an article about [Serverless Static WordPress on AWS for $0.01 a day](https://dev.to/aws-builders/serverless-static-wordpress-on-aws-for-0-01-a-day-1b29) not long ago. A penny a day – I can accept that. Let’s dig into this.

It turns out that this solution works like that:

1. You go to AWS CodeBuild and **run the job**
2. **The job starts an ECS container** with WordPress
3. You log in to WordPress to **make changes**
4. You click “Generate Static Site” in WordPress to **dump the website to the S3 bucket** and host it from there
5. **You run the job to stop the ECS container** (or pay for the running container that you don’t use and forgot to stop)

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ms06l2r8nr3bwbchitgj.png" alt="Serverless Static WordPress has an ECS container and RDS Aurora Serverless for editing content, and S3 with CloudFront and Lambda@Edge for service the website.">
  <figcaption>Serverless Static WordPress architecture ([source](https://www.techtospeech.com/serverless-static-wordpress-on-aws-for-0-01-a-day/https://www.techtospeech.com/serverless-static-wordpress-on-aws-for-0-01-a-day/))</figcaption>
</figure>

Well… That’s an interesting solution, but not appropriate for my needs.

With it, you are launching the CMS only when you need it. I see several drawbacks here:

- **You need to go to AWS to launch it and stop it.** Not optimal if you want to give access to CMS for someone not technical to write a new blog post.
- Even if you automate the above with some UI button, **you have to wait a minute or two for the container to start.**
- And lastly, you need to **remember to stop the instance** to not pay for it when you no longer use it. However, you could automate this with some inactivity timeout.

In short, the whole solution is a bit too complicated for hosting a simple website, and the user experience is not the best.

### Why not Webiny CMS
At that point, I resigned from using WordPress and focused my search on **“serverless CMS”**. Pretty soon, I came across [Webiny Serverless CMS](https://www.webiny.com/serverless-cms).

Webiny is an open-source project that you deploy to AWS. It takes advantage of serverless services to bring you self-hosted, customizable, and extensible CMS. It creates the infrastructure containing Lambda functions, API Gateway, DynamoDB, S3, and… Elasticsearch.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9oddvcptg599oyjowtdg.png" alt="Webiny CMS is a SPA frontend application with a bunch of Lambda functions, DynamoDB, Elasticsearch and Cognito on the backend.">
  <figcaption>Webiny API architecture ([source](https://www.webiny.com/docs/key-topics/cloud-infrastructure/api/overview/))</figcaption>
</figure>

**Almost everything deployed is serverless**, where you pay only for what you really use. **Except for the Elasticsearch,** [recently renamed](https://aws.amazon.com/blogs/aws/amazon-elasticsearch-service-is-now-amazon-opensearch-service-and-supports-opensearch-10/) to Amazon OpenSearch Service.

While you can have a small Elasticsearch/OpenSearch instance free of charge in the free tier for the first year, afterward, you have to pay **at least $13 per month**. I rejected Lightsail for $3.50 per month, so I won’t pay 4x as much for OpenSearch.

Nonetheless, I like the idea of Webiny and see a great use for it in the future. The good news is that [they work on the ability to make Elasticsearch optional](https://github.com/webiny/webiny-js/discussions/1899#discussioncomment-1282715).

## Headless CMS and Static Website
Eventually, I came with the solution. So why…?

### Why Prismic CMS
Finally, I looked for **“headless CMS”**. I quickly found out [there is a ton of them](https://jamstack.org/headless-cms/).

The general idea of a headless CMS is that it provides an editor to create content and an API that exposes that content. Then **the website fetches content from the API**. This is, by the way, the same workflow as with the Webiny described above.

Building a website with a headless CMS can be more work than with a standard CMS like WordPress. But at the same time, it brings some benefits, which I will cover at the end.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wrx2c5o58c0d1mlzn27m.png" alt="Prismic CMS provides a WYSIWYG editor and ability to define own custom blocks to build rich websites.">
  <figcaption>Prismic CMS UI ([source](https://prismic.io/non-developer))</figcaption>
</figure>
 
Some of the headless CMS services are open-source, and you host them by yourself. This is nice, but I don’t want to have an EC2 or even a Docker container running all the time.

Others are SaaS products, where you edit content and have access to the API. I reviewed a few of them and ended up choosing [Prismic](https://prismic.io/). Why? I heard of it before, docs looked good, and it has a free plan for one user as well as sensible pricing when the editorial team grows.

### Why Gatsby for Website
**[Gatsby](https://www.gatsbyjs.com/) is a static site generator**, one of the most popular ones. I think the peak of its popularity, when everyone was talking about it, was actually some time ago, but most days, I look at the frontend world from the distance.

Now, needing to build a website that will consume and display the content from an API, I decided to catch up and see for myself what’s the deal with Gatsby. **It’s based on React and has a lot of plugins**, which really speeds up development. Importantly, like every other major headless CMS, **Prismic provides a plugin for Gatsby**. That makes the integration effortless.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7to0nu502kqiolpue2k7.jpg" alt="Great Gatsby, the movie, not the statis site generator.">
  <figcaption>Oops, sorry, wrong Gatsby</figcaption>
</figure>

Gatsby produces a static website, meaning all the content is generated/fetched during the build and converted into **a simple HTML page you can host from an S3 bucket**. This gives an ultra-fast website that does not rely on any backend to make a bunch of database queries on each page view. However, on every change of content, you need to re-build the website.

## CI Pipeline for Headless CMS and Gatsby
With the tech stack selected, now it’s time to make it work.

As mentioned above, **you need to build the Gatsby website to update it**. This is necessary for two situations:

- you update the website code itself (e.g., website structure),
- or you update the content in the CMS.

Because of this first case, **I decided to build the website using GitHub Actions**, since this is my preferred CI system. Every time I push changes to the repository `main` branch, it triggers the build and deployment of the website.

Now, **I needed the build on the GitHub to happen also after changes in Prismic**. Thankfully, in Prismic, you can add a webhook to trigger after content edits.

In the perfect world, we could point the Prismic webhook directly to the GitHub API to trigger the build job. But the GitHub [`/dispatches` endpoint](https://docs.github.com/en/rest/reference/repos#create-a-repository-dispatch-event) requires an `event_type` parameter in the payload body. Unfortunately, we can’t add it to the messages sent from Prismic. However, as you probably know, **there is nothing we couldn’t patch with a Lambda function**:

```typescript
import {APIGatewayProxyHandlerV2} from 'aws-lambda/trigger/api-gateway-proxy';
import axios from 'axios';

const prismicSecret = process.env.PRISMIC_SECRET || '';
const githubUser = process.env.GITHUB_USER || '';
const githubRepo = process.env.GITHUB_REPO || '';
const githubToken = process.env.GITHUB_TOKEN || '';

export const handler: APIGatewayProxyHandlerV2 = async (event) => {
    const body = JSON.parse(event.body || '{}') as { secret: string };
    if (body.secret !== prismicSecret) {
        return {
            statusCode: 403,
        };
    }

    const response = await axios.post(`https://api.github.com/repos/${githubUser}/${githubRepo}/dispatches`, {
        event_type: 'prismatic_update',
    }, {
        auth: {
            username: githubUser,
            password: githubToken,
        }
    });
    console.log('GitHub response', response.status, response.data);

    return {};
};
```

When invoked from the Prismic webhook, this Lambda verifies the request by checking the secret token and calls the GitHub API to start the build.

The whole architecture looks as follows:

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubj15hudg6ut4h3thhg4.png" alt="Prismic webhook triggers Lambda function that starts a GitHub Actions execution. It builds and uploads the Gatsby static website to AWS S3 bucket, from where it's hosted by CloudFront.">
  <figcaption>Pipeline for updating Gatsby website after changes in Headless CMS</figcaption>
</figure>

In practice, the build and deployment in the GitHub Actions are handled by Serverless Framework, which deploys AWS services along with the website.

To make it all work, you need to configure few things:

- in **Prismic**, you need to **create a webhook** providing the HTTP API URL as a target and some secret value for authorization,
- in **Build Trigger Lambda**, you need to set:
  - the same secret value to validate requests,
  - GitHub username and repository name,
  - generated GitHub Personal Access Token to be able to dispatch builds,
- and finally in **GitHub**, you need to create repository secrets used in the build:
  - AWS access key ID and secret,
  - Prismic repo name and generated API token.

And that’s it. From this moment, every published change in the Prismic will trigger the build. The website will be updated in about 3-4 minutes.

You could optimize it by only building and uploading the website to the S3 bucket from the webhook instead of deploying the whole stack every time. Let’s make this a homework, shall we?

You can find the link to the repository with the entire solution at the end of the article 👇

## Real Cost of Static Website with CMS
**Is it really $0.00 per month?**

Yes. But also no.

There are no fixed or minimal costs:

- **Prismic** is free for one user,
- **GitHub Actions** provide free 2000 execution minutes per month (each build takes about 4 minutes),
- **HTTP API requests** cost $0.000001 per invocation, so to get billed $0.01, you would need to make at least 10.000 updates,
- **Lambda** is free up to 1M invocations and 400.000 GB-seconds of compute time per month (single execution takes milliseconds),
- **S3** with even 50 MB website and 100 file uploads also costs below $0.01 per month.

Thus you can update the website multiple times per month, and as long as no one visits it, you don’t pay a cent.

On the other hand, you may eventually begin paying for the website when people start visiting the website. **The only significant cost comes from CloudFront**, where you pay for requests count and total data transfer size. Pricing will depend on the CloudFront settings and the location of your visitors.

I think that’s fair – you pay only for the actual usage, as with other serverless services. But, if that cost becomes a problem, you can use another CDN in that place. [Cloudflare](https://www.cloudflare.com/) may be a much cheaper option here (even free).

## Is using SaaS CMS cheating?
Maybe.

Yes, I said I wanted to have everything hosted on AWS. But that means I don’t have to deploy anything elsewhere. Using an external SaaS solution is much less of a problem for me.

And yes, I’m eager to try Webiny as soon as they provide a built-in deployment without the Elasticsearch option. I will write a follow-up of this post then – subscribe to not miss it 😉

## Pros and Cons of Headless CMS and Gatsby
There are several benefits of this solution:

- **It’s easy to make changes.** Any non-technical person can modify the content. Even a technical one wouldn’t like to change the HTML and manually update the website every time few words need to be added to the page.
- **It’s so cheap it’s basically free.** You pay nothing for the existence of the website and making changes several times a month. Even with a lot of changes, it would be hard to spend more than few cents. You pay for the visits, like with any page you expose through CloudFront.
- **The website loads fast.** That’s the beauty and main idea of static site generators like Gatsby. No backend and database queries. Everything is already in HTML, hosted to you from the nearest CDN edge location.

However, there are also some drawbacks:

- **Changes are not visible immediately.** After every modification, the page needs to be built and deployed, which takes a moment.
- **The CMS is not as well-known and flexible as WordPress.** You can be almost sure that any writer will be familiar with WordPress. Here the Prismic CMS is not (yet?) well known, although it provides all the essential features needed in most cases.

I will leave the rest of the “WordPress vs. anything else” discussion out of this list. There is no universal answer to it, and everything depends on the use case.

## Final notes
As always, you can find a repository with the complete example on GitHub:

{% github m-radzikowski/aws-website-cms no-readme %}

If you are interested in **website analytics that cares about privacy and where you pay based on the actual usage** (just like in the solution the whole article was about), please check out the SiteClue. This post would not exist if I wouldn’t have to make this website:

[![SiteClue: Intuitive web analytics with privacy at heart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgmyuiw09iathbpv42bb.png)](https://siteclue.app)