Running Ghost blog on Azure Container Apps

Running Ghost  blog on Azure Container Apps

WordPress has become one of the most prominent website builders on the internet. It is free and is used by a lot of organizations and consumers. Millions of extensions and themes are built for WordPress. It's easy to install, easy to use, and easy to extend if necessary. But let's face it, it's also a hacker/spam/malware magnet because of its popularity and the many (unsafe) extensions.
But to be honest I was looking for something new. I am a Nerd and I want something to fiddle with.

After some searching, I stumbled upon Ghost. Ghost: The Creator Economy Platform. It looks beautiful, but I was a little bit turned down by the fact that I needed to run it on NodeJs and currently I also don't have a spare server lying around. Also, my knowledge of NodeJs is somewhat limited. Fortunately, I saw that they also offer a Docker image. ghost - Official Image | Docker Hub. Now Docker is a technology that I do know. :-)

I love Microsoft Azure so I immediately looked at what kind of cool options I have to host the Ghost Docker image. The most obvious option is an Azure WebApp running a Docker container.  But that's boring because it's familiar. So I thought let's try Azure Container Apps. Azure Container Apps | Microsoft Azure
With Azure Container Apps we basically have AKS (Azure Kubernetes Service) without all the complex infrastructure setup.

one does not simply deploy Kubernetes

But in order to scale up my new Blog, I must use a central database so that all my pods can access the same data at the same time. For example when you need to scale out.
I choose to use a MySql flexible server on Azure. (But you can also use a database container or even a local db file on Azure Storage)

az mysql flexible-server create --location westeurope --resource-group testGroup --name ghostserver --admin-user <<USERNAME>> --admin-password <<PASSWORD>> --sku-name Standard_B1ms 

Next is to set up shared storage for the content/media files, etc. Otherwise, the content can only be loaded on a local pod. So if you have multiple instances of your website, it's essential that all pods can access the same images and other files.
So first we create a storage account.

az storage account create \
    --resource-group <<RESOURCE GROUP>> \
    --name <<ACCOUNT_NAME>> \
    --kind FileStorage \
    --sku Premium_LRS \
    --output none
azure cli code for creating a storage account

Next, we use that storage account to create a file share

az storage share-rm create \
    --resource-group <<RESOURCE GROUP>> \
    --storage-account <<ACCOUNT_NAME>> \
    --name <<NAME OF THE FILE SHARE>> \
    --access-tier "TransactionOptimized" \
    --output none
azure cli code for creating a file share inside storage account

In our next step, we want to link our file-share storage to the Azure Container App environment.
The Azure Container Apps environment acts as a secure boundary around a group of container apps.
But in order to link that storage account to the environment, we first need to create that environment ;-) like so...

az containerapp env create \
  --resource-group <<RESOURCE GROUP>> \
  --location westeurope
azure cli code to create an Azure Container App Environment

Now we need to configure the newly created Azure Storage as a storage mount in your Azure Container App. For that, you can use the following code.

az containerapp env storage set \
  --access-mode ReadWrite \
  --azure-file-account-name $STORAGE_ACCOUNT_NAME \
  --azure-file-account-key $STORAGE_ACCOUNT_KEY \
  --azure-file-share-name $STORAGE_SHARE_NAME \
  --storage-name $STORAGE_MOUNT_NAME \
  --resource-group <<RESOURCE GROUP>> \
  --output table
code to link your Azure File Share to the Container App Environment

Now we have created a storage mount, to our Azure File Share, inside our Azure Container App environment. With that in place, we can configure a volume for our containers in our YAML templates.

    - image: ghost:latest
      name: my-container
      - mountPath: /var/lib/ghost/content
        volumeName: azure-files-volume
    - name: azure-files-volume
      storageName: storagename
      storageType: AzureFile
example snippet 

Now let's create a YAML template in which we specify all the details of our Azure Container App. Note: You can also see the details for the Azure File Share that we created.

 type: Microsoft.App/containerApps
 resourceGroup: <<RESOURCE GROUP>>
    - env:
      - name: database__client
        value: mysql
      - name: database__connection__host
        value: <<DATABASE HOST>>
      - name: database__connection__user
        value: <<DB USERNAME>>
      - name: database__connection__password
        value: <<PASSWORD>>
      - name: database__connection__database
        value: <<DATABASE NAME>>
      name: <<YOUR APP NAME>>
        cpu: 0.25
        ephemeralStorage: 3Gi
        memory: 0.5Gi
      - mountPath: /var/lib/ghost/content
        volumeName: azure-files-volume
    initContainers: null
    revisionSuffix: ''
      maxReplicas: 2
      minReplicas: 1
      rules: null
    - name: azure-files-volume
      storageName: <<STORAGE NAME>>
      storageType: AzureFile

Replace all the <<PARAMETER>> parameters with your own details. We use that file to create our new Azure Container App. Let's call that file containerapp.yaml
Now we use that file to create the container app.

az containerapp create \
    --name <<YOUR APP NAME>> \
    --environment <<YOUR ENVIRONMENT NAME>> \
    --resource-group <<RESOURCE GROUP>> \
    --yaml containerapp.yaml

Finally, we have to wait for Azure for finishing up our Azure Container App.

In order to see what is going on inside your Azure Container App, you can easily navigate (through the Azure Portal) to your Azure Container App logs.

In order to connect to the MySql server, we must configure SSL in our environment variable.

- name: database__connection__ssl__ca
  value: -----BEGIN CERTIFICATE-----\n...bla bla bla...\n-----END CERTIFICATE-----\n

You can find the certificate in your Azure Database MySql Networking settings.

Use storage mounts in Azure Container Apps | Microsoft Learn
Ghost: The #1 open source headless Node.js CMS
GitHub - docker-library/ghost: Docker Official Image packaging for Ghost
Quickstart: Deploy your code to Azure Container Apps | Microsoft Learn