Self-hosted Blog Guide for Engineers

A comprehensive guide to setting up a self-hosted blog with the use of Terraform, Ansible, Kubernetes on Ghost platform with MySQL.

I'd been searching for a comprehensive guide on setting up a cheap and easily maintainable personal site or blog lately and couldn't find a good enough one. I've decided to do it myself and create a step-by-step guide for others to reuse. Welcome to a self-hosted blog development tutorial utilizing a heck of a technology stack: Terraform, Ansible, and Kubernetes! As the first tutorial on Humble Thoughts, it's available to all-tier members but serves as an example of exclusive content available in the future only to the Exclusive subscription members. You can sign-up for a trial period and decide later if you want to continue supporting me.

Yeah, you've got this right; we will be using a stack that might look like a bit of over-engineering for this type of solution, but for a good reason — it's always great to learn and practice something new. Still, I was impressed by how easily one can maintain the result solution. And even though the stack may seem like an over-kill for the problem, I keep it as simple as possible, though easy to maintain and scale further, and relatively cheap as well (only around 5€/month according to the Hetzner Cloud prices in Jun 2023).

I'll guide you through the process of setting up servers (single-node or even a cluster) on the cheapest cloud platform provider (Hetzner) I know, with the use of Terraform, Ansible, and k3s (the Lightweight Kubernetes). The project we will be deploying is a Ghost blog, similar to the one you're reading this tutorial on.

My goal in this tutorial is to guide you through the process of setting up a web service using the mentioned tech stack. I will only dive into some of the details, but in general will cover only necessary things to keep the tutorial short and focused on practicalities. You won't see many theory on how Kubernetes or Ansible work.

Having a little experience with such tools and services as CLI terminal, SSH keys, Git, Docker, Python dependencies manager (pip), AWS, and GitLab would improve your chances of faster results, but I will leave notes for you to be able to do your own research on your way to the final setup. Besides, I prepared a Git repository with all the necessary code snippets you will need for the tutorial.

Here's the plan for this tutorial:

  1. Setup tools and services
  2. Design the target solution architecture to better understand the end goal
  3. Initialize the project on Hetzner and provision infrastructure
  4. Configure the provisioned server with Ansible setting up K3s
  5. Configure and deploy blog components using k3s

Let's jump in and get our hands on the tech without further ado!


The tutorial is created on MacOS; thus, it will be possible to repeat all the steps without a difference on Linux, but I wonder how easy it will be to complete on Windows. All the tools are available on Windows, but the way to set them up might differ, so keep that in mind.

I will use several tools and services during the tutorial, so it's better to start preparing them in advance just not to block further steps.

We will use the following services, so make sure you have accounts on all of these:

  1. Gitlab for storing our infrastructure code and Terraform state. Create an account there and leave it as it is for now.
  2. Hetzner Cloud is a cheap (probably the most affordable) cloud provider with a decent provider selection for Terraform, Ansible, and K8s. Create an account there and leave it for now. We will get back there when the time to create a new project comes.
  3. You will need a domain name for this tutorial for the webserver to work with Let's Encrypt. You can skip this if you can reconfigure Nginx Ingress to proxy requests to the blog service by IP instead of the domain name. I recommend using Namecheap, one of the cheapest options, if you need a spare domain name for experiments.
  4. (optional) AWS can provide you a wide range of cloud services. You might need an SES service to send transactional emails from the blog.

Got them? Well done! We're almost ready to start; let's set up the necessary tools on your machine.

  1. We will use Terraform for cloud infrastructure provisioning and management, so you will need a CLI tool from them to apply changes to the infrastructure on the Hetzner cloud right from your machine (CLI terminal). Make sure to install one following the official documentation. I suggest going with the Homebrew installation if you're using MacOS.
  2. The following necessary tool will be Ansible. It will help us configure the provisioned servers in an automated way so that we won't need to manually log in to every single machine and set up all the necessary tools on the servers. It saves much time automating such routines as configuring firewalls, and web servers (e.g., nginx), updating apt-packages, and much more. Ansible is a Python-based tool, so it requires Python installation on your local machine, and it's all covered in the official installation guide, so please follow that and install Ansible. By the way, it's also available in Homebrew, so if you went for the Homebrew option for Terraform, I suggest doing the same with Ansible.
  3. There's an optional but excellent addition to the Python-driven projects, which our project is because of Ansible, — virtualenv toolset. Generally speaking, it's a way to split a global Python dependencies workspace into many separate and isolated spaces based on projects. It allows you to create isolated virtual environments for Python dependencies and ensure they never collide with the global ones. I suggest installing virtualenvwrapper and acquiring such new CLI commands as mkvirtualenv and workon.
  4. Kubernetes will allow us to manage the cluster (even if it will be a single-node cluster). To use it, you will also need a local CLI tool installation (kubectl, to be precise), which is also available in the official documentation.

That's it! Now we're ready to move forward and experiment with the cloud!


Solution Architecture

Let's start from the point where every software project should begin — solution architecture. Every solution architecture starts from requirements. Let's keep our requirements simple for a smooth start. So, our requirements, for now, are a single-node server on Hetzner Cloud, running a k3s server and services, such as Ghost blog, a MySQL database for the blog, and a persistent volume from Hetzner to make sure our data is stored consistently and won't be gone if a Ghost pod or MySQL pod is replaced. We also want to allow only specific ports: 80/443 for HTTP(S), 6443 for K8s Server API, and 22 for SSH.

Here is a diagram of what such solution could look like:

System Architecture diagram

Let's briefly go through the main component of the solution. The big red box represents Hetzner Cloud, and the orange blocks represent particular Hetzner Services (Firewall, Server, Volume). The Server is the most exciting part for us because it contains the main high-level logical aspects of the system — a webserver (nginx), Ghost blog, and the database for it. We will primarily focus on the green components in our configuration but also touch the K8s API server just a little bit. We will set up an automated Let's Encrypt certificate issuer to provide an SSL certificate for our service. We will also need a K8s Ingress Service to route all the incoming requests and make sure the HTTP(S) requests are proxied correctly to the right services — Certificate Issuer and the Ghost App. You can think about Ingress as a routing service with a compelling set of configuration tools, some of which we will save for the future. It will only help us expose the mentioned services and apps for now.

Let's code

We will start with setting up the project locally and setting up all the necessary connections to the services I listed above. To simplify the process, I've prepared a template repository on Github and Gitlab, containing all the required files we will work with during this tutorial. Fork it to a private repository and continue with your copy to keep all the changes you make versioned. Check out the repository locally and open it in your favorite code editor. Note that the repository files contain places marked with TODO comments that you have to change to your own settings otherwise it won't work at all.

This post is for subscribers only

Already have an account? Sign in.

Subscribe to Humble Thoughts

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]