There are many tutorials on the web that describe more or less automated ways of deploying packaged software to Azure Web Apps. For this particular one I’ve decided to use the popular Content Management System WordPress.
What many such applications have in common is that there is an initial installation procedure, consisting of copying files to a web host and configuring database access, followed by a web-based graphical installer that sets up all of the application configuration.
In the case of WordPress, this is known as the “famous five minute installation process”.
I’ve often wondered whether it would be possible to combine provisioning the resources in Azure, deploying the web site assets, and running through the post-deployment configuration in a single step. It’s quite common for such applications to provide a command-line interface for managing the system, and WordPress is no exception. The WordPress CLI is known as WP-CLI. WP-CLI isn’t included in the main WordPress distribution and must be downloaded separately. WP-CLI can do most actions that can be done through the WordPress admin UI, as well as a few that can’t.
Before going any further, it’s worth pointing out that this is probably not the best way to install and configure WordPress for your personal website1 . WordPress isn’t really the point of this discussion, I’m just using it as an example of a packaged web application with a graphical installer.
The main script is a shell script that uses the Azure CLI to create and configure resources. You’ll need to connect with az login
or equivalent before running it. There are a couple of other dependencies that may not be present in all Linux distributions, notably expect
, but you should be able to install these with your package manager. I’ve only tested this on Fedora Silverblue, but there’s no reason why it shouldn’t work in bash
on OSX, WSL, or even a build agent, which is probably where you want to be doing things like this.
The script creates all the required resources, namely an App Service Plan, a Web App, and a MariaDB server and database. All the config information required by WordPress is stored in AppSettings in the Web App. In “real life”, you’d want to use Azure Key Vault references for many of these app settings, as under the current arrangement the secret values will all be echoed to the console, but I’ve omitted the key vault here in the interest of brevity.
All the settings are read from environment variables, if you have a file named .env
in the same folder as the script then its contents will be sourced. I have included a .env.example
file with all the variables the script requires.
The WordPress code is downloaded in a zip file and deployed with az webapp deployment source config-zip
, which deploys directly to the site. There is an app setting project
that specifies which subfolder of the zip file we want to extract.
Rather than ssh
ing directly to the Web App instance, the script attempts to use az webapp create-remote-connection
to connect to the running app. This isn’t particularly reliable, but I’ve attempted to work around the worst symptoms in the script. These workarounds involve a combination of waiting for things to start, killing processes by name to clear up old connections, and other classic techniques. It does appear there are fixes in this area of Azure CLI in development. My workarounds still aren’t 100% reliable, the ssh tunnel sometimes fails, but will generally work the second time the script is run. Any suggestions for improving this would be welcome.
Having created the tunnel, we need to ssh
with a password, which is set in the web app image. Searching StackOverflow for techniques to automate password-based ssh
will uncover a large number of suggestions that this is a bad idea, some more polite than others.
In our case, since we have no alternative, we use expect
to automate the sending of the ssh
command and responding to the Password:
prompt. We also specify a couple of extra ssh
options with -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no"
to suppress error messages about unknown hosts.
Once ssh
is working, we upload a wp-config.php
file which is pretty similar to the boilerplate one, but with changes to enable SSL between the browser and the site, as well as between the site and the database2. The configuration values which would be hard-coded in this file in a “classic” WordPress installation are also moved out to environment variables - created by App Settings in the web app - and read dynamically.
Since the MariaDB server is only accessible from Azure IPs, we ssh
again to use the mysql
client provided inside the web app to log in and configure the WordPress database and user.
Finally we download the WP-CLI .phar
and place it in the path. By convention the downloaded file is renamed to wp
. The wp
client can then be used to configure our new installation. By far the most difficult part of this script was getting the quoting and escaping right for strings that are parsed by bash
, by expect
, by bash
again, then by WP-CLI (i.e. php
), and then by the WordPress API. This is the reason the expect
interactions are broken out into three separate blocks.
At the end of the script, the site should look like this:
I’ve created a multi-file gist with the main script, along with the wp-config.php
and .env.example
files. You can clone the whole repo with
git clone https://gist.github.com/gavincampbell/f59f61cf98ae597dc56a5bce5b830cea some_folder
If you’ve stumbled on this page whilst searching for instructions on how to set up WordPress for your personal website, you could do worse than looking at gandi.net. They do all kinds of things, including WordPress hosting. If you sign up through this link you get some kind of discount, and they give me a small credit on my existing bill. This site isn’t hosted there, but I have others that are, and all my domains are registered with them. ↩︎
Although WP-CLI can create this config file, it seems that it can’t create the file until it has connected to the database, but it can’t connect to the database until it has read the SSL options from the file. ↩︎