Continuing the topic of hosting Ghost on Azure, I decided to document some nuances of connecting to Azure Database for MySQL from a Ghost Docker container hosted on Azure Web Apps for Containers. Moreover, in this post, I will shed some light on doing it securely from any Node.js application.

If you are more interested in the technical details, feel free to skip my reasoning for using Azure Database for MySQL as a backed for Ghost in the first section and scroll straight to the second one.

Why fall back to managed MySQL server from the multi-container app

In Dec 2020, when I come up with the idea of hosting Ghost on Azure as a multi-container app using Docker Compose support in Azure App Service, that configuration worked fine. I was able to reduce the application hosting costs by hosting MySQL as a container on the same App Service plan as the Ghost container itself. However, starting from May 2021, I received a few notifications indicating that the original deployment configuration stopped working, and the following error occurred in App Service container logs:

ER_HOST_NOT_PRIVILEGED: Host ‘172.X.X.X’ is not allowed to connect to this MySQL server

Nothing was changed in the deployment configuration, and I started looking for possible root causes of that issue.

As the multi-container support on App Service is still in preview, changes to that service might have caused the containers to crash. Unfortunately, I didn’t find any mentions of service updates in the official docs or on community resources.

Another possible cause of the error could be some updates to the MySQL container image – I was using the image with the ‘mysql:5.7’ tag and didn’t stick to a specific build in that minor version.

The attempts to redeploy the app using a specific MySQL container image that was up to date when I created the original Docker Compose configuration didn’t succeed. Neither successful were attempts to upgrade the setup to using a MySQL 8 container. Also, it is worth mentioning that the same configurations that produced errors while running on App Service worked like a charm in a local Docker Desktop environment, so the error wasn’t related to the well-known MySQL permission nuance when a user account can connect to the server from localhost only.

After troubleshooting the issue for a few days, to say that I was confused was as to say nothing. Continue playing a game of numbers when you don’t even know the details of how Docker networking works in App Service didn’t seem to be reasonable. So, it was high time to migrate to something more stable, supported and well-documented.

Setting up Azure Database for MySQL

For sure, you can go to the Azure portal and use the provisioning wizard to create a new instance of the Azure Database for MySQL. However, that approach didn’t fit into my concept of having unattended, reproducible and easy-to-use one-click deployment. As I already implemented that concept with ARM templates and the Deploy to Azure button, it was natural to refactor the existing deployment templates to use a managed MySQL database instead of the MySQL container. Apart from that, as Azure Bicep – a new declarative language for Azure resource deployment reached its general availability, I decided to migrate my deployment configuration to that new format completely.

The Bicep team has already published many Bicep snippets for Azure resources, so you can use them as a reference.

After some trials and errors, my resulting configuration for Azure Database for MySQL resource looked like the following:

Leaving the Bicep syntax nuances aside, the resource configuration itself has a few important aspects to pay attention to. First of all, it enforces the usage of encryption for server connections and sets the minimal version of TLS for incoming connections. Secondly, it configures server firewall rules to accept incoming connections from Azure services only. Lastly, it uses password authentication.

The database password is specified during a template deployment and saved in an Azure Key Vault. The App Service hosting the Ghost application uses that password via Azure Key vault secret reference to authenticate to the database.

Now let’s look into the updated Ghost app configuration.

Configuring Ghost app for Azure Database for MySQL

The Ghost team provided really comprehensive documentation on how to configure a Ghost database connection to MySQL. That documentation even contains a detailed description of different client SSL options. In order to pass that nested JSON key structure as configuration for Azure App Service, all semicolons should be replaced by __ (double underscore) so that the same configuration will be represented as key-value pairs in the app settings.

Apart from that, Ghost as a Node.js application also relies on the native runtime TLS options. That said, in the SSL section of your config, you can specify all ‘tls.createSecureContext’ options. For example, in addition to providing your custom certificate (if needed), you can set the minimum and maximum TLS versions to be used when establishing connections:

Pay attention that the ‘secureProtocol’ configuration option widely referenced on the Internet is deprecated and should not be used.

Please note that the Baltimore CyberTrust Root certificate is included in the Mozilla Included CA Certificate List. You don’t need to download and embed it in your Ghost configuration as documented in the Azure Database for MySQL documentation (Configure SSL).

Try sample configuration

After lots of work on migrating from ARM templates to Bicep and configuring the Ghost app to use Azure Database for MySQL, the new one-click Ghost deployment on Azure App Service is finally ready. You can easily try it out by clicking on the Deploy to Azure button in the project repository on GitHub.

As Bicep makes it much easier to build modular templates, I discontinued two separate templates for deployments with Azure CDN and Azure Front Door and created the single deployment templates with conditional deployment logic – you just need to select the desired option:

If you experience any issue with the deployment, please submit an issue on GitHub or post it in the comments below 👇

Stay tuned to hear about my impressions on new authoring experience for Azure deployments using Azure Bicep 😉