Ghost Blog on Azure Kubernetes Service (AKS)

This site (cmcrowell.com) is a ghost blog running on Azure Kubernetes Service (AKS). I think there are less expensive ways of hosting a website, but that's a story for another time 😉

One of the niceties of running this way, is that updating ghost is just a matter of changing the image in the deployment manifest. Here's the TL:DR:

alias k=kubectl

k edit deploy ghost

# replace ghost:4.41.0-alpine with ghost:5.2.2-alpine

The more convoluted part I tend to do is building my own image and pushing that image to Azure Container Registry (ACR). Why do I do this? Well, for two reasons. First, it allows me to customize my ghost blog to include the casper theme and add the ability to comment below each article (scroll down and you can see for yourself). Second, it allows me to control the version and release, instead of just taking the latest tag (which you should never do), and automatically updating to the current release. Nonetheless, here's how I do it:

Building Your Image

The first gotcha is that when you clone down the repo for ghost at https://github.com/TryGhost/Ghost, they have symlinked the ghost/core/content/themes/casper directory to another tagged release, which creates a problem. The problem comes when you build your image via dockerfile, the build process fails because the files don't exist (hence, the symlinks).

For this, I simply click on the link in GitHub (picture above), taking me to the proper  tagged release of the casper theme. Then I change to the cloned directory and clone the casper release inside there. It looks something like this:

clone the Ghost repo from GitHub, then replace the casper directory. 

We can see that this directory now contains the files needed to build our image.

In the themes directory, I added two things before creating my Dockerfile and building my image. First, in the casper/post.hbs file, I added the following in order to allow comments at the end of each post (like this one):

	<div id="disqus_thread"></div>
        <script>
            var disqus_config = function () {
                this.page.url = "{{url absolute="true"}}";
                this.page.identifier = "ghost-{{comment_id}}"
            };
            (function() {
            var d = document, s = d.createElement('script');
            s.src = 'https://atxlinux.disqus.com/embed.js';
            s.setAttribute('data-timestamp', +new Date());
            (d.head || d.body).appendChild(s);
            })();
        </script>

Also in the themes directory, I created a file named config.production.json and it contained the following:

{
    "url": "http://localhost:2368",
    "server": {
        "port": 2368,
        "host": "0.0.0.0"
    },
    "database": {
        "client": "sqlite3",
        "connection": {
            "filename": "/var/lib/ghost/content/data/ghost.db"
        }
    },
    "mail": {
        "from": "'Chad Crowell' <chad@mg.cmcrowell.com>",
        "transport": "SMTP",
        "options": {
            "host": "smtp.mailgun.org",
            "port": 587,
            "auth": {
                "user": "postmaster@mg.cmcrowell.com",
                "pass": "e451ea22f95-2a9a428a-e8281288"
            }
        }
    },
    "logging": {
        "transports": [
            "file",
            "stdout"
        ]
    },
    "process": "systemd",
    "paths": {
        "contentPath": "/var/lib/ghost/content"
    }
}

The addition of the mail configuration allows Ghost to send transactional emails such as user invitations, password resets, member signup, and member login links. Read more about custom configuration files like this one here: https://ghost.org/docs/config/

Now it's time to create our Dockerfile!! This is a pretty simple Dockerfile, in that it takes the entire casper directory (in the current directory) and copies it to /var/lib/ghost/current/content/themese/casper inside the container. It also takes config.production.json and copies it into /var/lib/ghost/config.production.json inside the container. files from your current directory and copies them into the container. It looks like this:

FROM ghost:5.60-alpine

# copy themes/config to container
COPY casper /var/lib/ghost/current/content/themes/casper
COPY config.production.json /var/lib/ghost/config.production.json

Finally, we can build our image and push to our Azure Container Registry. If you don't already have a container registry, create one using the following commands, assuming you have azure-cli installed.

# set a few environment variables
# e.g. name your resource group "container-rg"
# e.g. name your container registry atxlinux
ACR_RESOURCE_GROUP=container-rg
ACR_NAME=atxlinux

# create a resource group in the east US region
az group create --name $ACR_RESOURCE_GROUP --location eastus

# create a container registry
az acr create --resource-group $ACR_RESOURCE_GROUP --name ACR_NAME --sku Basic --admin-enabled true

Learn more about creating a private azure container registry here.

Luckily, we can build our image and push to our registry in one single command by using Azure ACR tasks.

az acr build --image atxlinux-ghost:v1 --registry $ACR_NAME --file Dockerfile .

Congratulations! 🎉 Now you've built and pushed your new customized ghost image to ACR (Azure Container Registry).

Just like in the TL:DR section above, we can edit our deployment and change the  image tag.

Let's recap!

  • Clone the ghost github repo
  • Clone the casper theme github repo inside of the themes folder
  • Modify the post.hbs file
  • Create the config.production.json file
  • Create a Dockerfile
  • Build and push your new image to ACR
  • Edit the ghost deployment in AKS

If you liked this article, please let me know by commenting below. If you would like to see how I built my ghost blog on AKS, please let me know as well.

Thanks for reading! 🤠