Overkill Blog Setup

Conner Bradley
Conner Bradley
3 min read

It's been awhile! As you can see, we have yet another blog setup. This time the architecture is absolutely ludicrous compared to before. Previously, my website was hosted from a single server that was multi-purpose. Too many points of failure, and too many shared services on one machine. So, how have we improved?

Revised architecture. This new design leverages ghost as a CMS; however, the actual frontend is a swarm of NGINX nodes that serve static content. In reality it isn't exactly like this, but I made some changes to make the diagram more digestible.

Infrastructure: Swarm

As a backbone I am ditching the single server / single VM stack. We need redundancy and fault tolerance! I stood up 4 new VMs and provisioned them with a hefty ansible playbook. Effectively, the base provisioning process is as follows:

  • Add common users
  • Reconfigure SSH to be secure
  • Install docker
  • Set up docker swarm cluster. The ansible hosts configuration denotes one host to be a manager, and the other three become secondary nodes
  • Configure GlusterFS mounts

The GlusterFS mounts are used for static assets like images, and these GlusterFS mounts are shared between the cluster nodes and the ghost admin interface. A NGINX load balancer sits in front of all of this and distributes traffic evenly between the replicas.

CMS: Ghost

Next we have the CMS. I like editing markdown as much as the next person, but having a nice web UI that's automated is 1000 times better then scaffolding posts in markdown and then compiling them. It's also worth mentioning Ghost supports markdown, which is excellent for porting my old posts over to it.

It's worth noting ghost does provide a frontend and theming service, and we are explicitly not using it. We are using ghost as a headless service which exposes a graphQL API. That GraphQL API is then used by Gatsby as a content source.

Repository: Gitlab

For the current setup, I'm using my gitlab instance to host the Gatsby sources for the website theme. There's no content in this Gatsby source tree, all of that is pulled by Gatsby through a ghost GraphQL integration. In a way, you can consider the content to be decoupled from the interface. The Gatsby interface is generic and re-usable, and the content is just provided through the API.

Gitlab also provides a CI/CD framework that can be invoked on commits and through webhooks. With that in mind, I can simply use a temporary docker container that builds my Gatsby sources, then a container that will copy the compiled files and serve them.

# Responsible for building our gatsby site
FROM node:lts-buster-slim as build
RUN npm i -g gatsby-cli
RUN mkdir -p /app
ADD . ./
RUN npm install
RUN gatsby build

# The gatsby image is simply a NGINX wrapper
FROM gatsbyjs/gatsby
COPY --from=build /app/public /pub
RUN mkdir -p /pub/content/images

Once the container is built another step uploads the container to the swarm's private container registry, and then uploads the swarm with the image. Note that we have to use the same process in cases where the Gatsby codebase is updated or the site content is updated. Gatsby fetches the site content from the ghost API at compile time, so when the content is updated the site has to be re-compiled as the final product is ultimately a static site.


Well, we ultimately built one of the most ridiculous personal blog stacks in my opinion. This website gets barely any traffic (you can change that, reader) for one node, let alone 4. Being an infrastructure nerd I quite liked building out this project, and I am quite happy with the results!

What would you do differently? Are you part of the group that thinks docker swarm is dying (well it kinda is)? Do you think I should have just hopped on the kubernetes hype train? Let me know down in the comments once I enable that integration 😛

Source code coming soon!