Setting up my Blog, Part II

The template worked a treat, I had Ghost running on PaaS MySQL and a Linux Container App. I tabbed back to my Azure control panel and deleted the new resource group and resources.

Setting up my Blog, Part II

In Part I of this blog setup series, I summarized some of my past attempts at setting up a blog and the mistake I’ve seen myself, and I suspect many other ‘homelabbers’, make countless time. Setting a scope so broad that there is little hope for success from the outset. My original goal for this project was to get a blog up and published but if you looked at my to-do list, you wouldn’t be wrong to think I cared more about the tech stack then the outcome. In this update, I’ll cover the technology stack I thought I was going to proceed with, my process for getting it setup and some of the interesting points along the way. This post is here to elaborate on the process I went through with Azure but, ultimately, I settled on hosting my blog with Digital Ocean in a Ghost droplet using Cloudflare for WAF and CDN.

I’ll start with some of the research I did into alternative blog platforms and why I ultimately chose Ghost. My requirements for a blog platform are relatively simple. I want something that I can self-host. I want something with enough of a community that I can get answers when I search for them. I’d prefer something with available themes, free or paid, or, at least a clean and usable default layout. I’d prefer something extensible so I can add features without having to work too hard. I like writing in markdown and prefer a plaintext editor over a WYSIWYG editor.

Ghost meets all of those requirements and many of the platforms I’d previously mentioned like Hexo, Jekyll, Hugo and the miniblog template from Microsoft dotnet interns do as well. Ghost’s editor is by default WYSIWYG but you can choose to work in markdown mode. I write the majority of my posts in markdown in a text editor from either my phone, iPad or desktop and then just paste them in. It’s distributed under an MIT license and has excellent documentation. The ultimate selling feature ended up being that several Microsoft employees, MVPs and other Cloud Architects seem quite happy with the platform.

From there I needed to choose my hosting option. I won’t rehash much of what I’d covered in my previous post but this is often where I get into trouble with personal projects. Ghost offers their own SaaS hosting option and they have documentation for using Digital Ocean and Linode as a third-party host. They also have detailed instructions for self-hosting on Linux, Windows, Mac and Docker. I’d previously decided I wanted to host in my personal Azure subscriptions that I maintain for as an extension to my homelab.

I first investigated hosting the platform directly in an Azure App Service Plan as a webapp. I found blog posts from Radoslav Gatev and Yannick Reekmans documenting how they were able to do that by creating a template that is ready to deploy to Azure. It seemed likely that this was going to require regular maintenance and updates to the repo, which I wasn’t prepared for, so I decided to give this a skip.

I later found blog posts from Jessica Deen, Gareth Emslie and Andrew Matveychuk documenting their process of deploying Ghost to Azure using App Service Plans and Container Apps which seemed a better fit for me. Andrew’s process in particular seemed a good fit for my existing Azure environments and he’d done the work to create BICEP IaC, which is my current preferred language.

Andrew’s template worked a treat and within about 30 minutes of finding his post, I had Ghost running on PaaS MySQL and a Linux Container App. I was able to login and start poking around the ghost control panel, make some test posts and could have called it good enough. I tabbed back to my Azure control panel and deleted the new resource group and resources.

I wasn’t happy with the cost, the naming convention didn’t match my existing one and the infrastructure as code was good, but didn’t meet my requirements for granularity. I already had a Key Vault, App Service Plan, log workspace and several other resources. If I’m going to create Azure Front Door, I want it to be reusable for other projects. If I’m going with Azure, I want my container images in Azure Container Registry, not Docker Hub.

I’m going to take a slight tangent here. As I described in Part I, the goal of this exercise was to get a blog up and running, not to practice my IaC skills or figure out how to best leverage Azure while minimizing costs. Those are key parts of my skill set and elements of my day-to-day work that I regularly aim to refine, but this effort wasn’t intended as an exercise. My goal was to get to a Minimum Viable Product and here I was focusing on infrastructure, deployment methodology and sustainability. In the consulting circles I work in, there is a significant push to get MVPs out the door as quickly as possible and it can often feel like the objectively ‘correct’ choice. While I wish I’d taken that advice in this case and just had my blog up and running several days sooner, I think doing so for real-world engagements is a mistake. As architects, one of our most critical jobs is ensuring the platform or workload is built, deployed and operationalized in a sustainable manner. A lot of our most important work happens before any code is deployed. Setting those expectations at the outset and building in those best practices into the process is the only way to stay ahead of that technical debt.

Back to my mission, I’d decided to break the various templates I’d found into a few chunks. Infrastructure that wasn’t specifically related to this project, namely the Azure Container Registry, log analytics workspace and the Front Door basics, I removed from my repo entirely, it would go into my existing infrastructure repo and pipeline that I manage in Azure DevOps. I can pass values for those resources from a parameters file to any new resources.

Infrastructure specific to Ghost like the storage account, key vault, app service plan and MySQL server went into a module, ghost.bicep, that would be called from a primary template, main.bicep. Updates to the core infrastructure, like the Front Door, went into a separate module, infra.bicep. I also chose to use Azure Front Door Standard (preview), instead of the classic front door as it’s easier to manage and has several benefits. I recommend John Savill’s excellent video on the subject for some of the benefits and new features. I’ve also rewritten the naming convention specified in bicep variables to more closely match my preferred approach.

Andrew’s blog post and repo are targeted at helping others quickly deploy their own installation of Ghost and so he is using compiled ARM template and a ‘Deploy to Azure’ button that accomplishes that goal nicely. My repo, while public, is intended mostly to serve me. My deployment methodology reflects my preferred approach, Azure DevOps Pipelines. I created a multi-stage yaml pipeline, blog-ci.yml that includes steps that validate the bicep and then deploys it to Azure. It’s currently configured to use a pipeline secret for the database credentials, but typically I would be using a key vault read task for this.

I was also testing my own build of the ghost docker container that adds support for App Insights. I was able to get it working and deploying to my Azure Container Registry reliably but while troubleshooting, I switched back to Andrew’s public docker repo as it eliminated some variables in troubleshooting. Those details, including my ADO pipeline for deploying the container image to the ACR are in their own repo on my GitHub, docker-ghost-ai.

After some tweaks and testing, I got it all deploying reliably using my new infrastructure as code and Andrew’s docker image. I have long-term plans to go back and fix a few issues that remain. Andrew’s deployment relies on a PaaS MySQL resource that is quite costly for a small blog, triple what just hosting through Ghost SaaS would cost. Multi-Container Apps are likely the best way to fix this but I couldn’t get it working in the time I had. The whole solution was going to come to around CAD60/month and while I have Azure credits that can cover that, I use those for testing.

In Part III, I got through the decision and setup of using the ~$6/month Ghost droplet on Digital Ocean and my existing free Cloudflare account for CDN and WAF.

Mastodon