Deploy a WordPress Azure Web App with an Alternative MySQL Database

I was recently presented with an interesting question about Azure Web Apps, WordPress, and MySQL. While not necessarily a “hard” question, the answer wasn’t as readily available as I first anticipated. I thought I would share my experience here in hopes of helping others.

The Question

How can you deploy a WordPress site using Azure Web Apps that uses a MySQL database instance that is not a ClearDB database available in the Azure subscription?

Background

Normally when you create a WordPress site using Azure Web Apps you are presented with an option to select an existing ClearDB MySQL database, or create a new one. But what if you don’t want to use an existing instance or create a new one? What if you want to use a MySQL database instance deployed to an Azure VM or you have a ClearDB MySQL database that doesn’t show in the Azure Portal (e.g. one of the ClearDB Basic offerings)?

The Answer(s)

Like most technology related questions (or life in general), there are a few ways to solve this challenge. There is the “easy” way, and there is the more powerful, yet slightly more complicated, some would argue the “right” way.

The Easy Way

The easiest approach is to create a WordPress site with Azure Web Apps and select either an existing ClearDB database or create a new ClearDB database. Once the WordPress site is deployed, you can then change the database connection string in the wp-config.php file to be the database you want (e.g. a ClearDB Basic instance or a MySQL instance on an Azure VM).

  1. Let the WordPress site be deployed, but do not complete the installation. In other words, once the site is deployed, browsing to the site’s URL should result in the standard WordPress default installation prompt.
  2.  
  3. Open the Kudu console by going to http:[your-site-name].scm.azurewebsites.net. If you’re already signed into the Azure Portal, you should proceed through without any authentication challenge. Otherwise you’ll be challenged for your authentication credentials.
  4. Navigate to the Debug console (via the menu on the top). Browse to the \site\wwroot\ directory.
  5. Edit the wp-config.php file by clicking on the pencil icon to the left of the file name. Doing so will switch to an edit view for the file. Don’t click on the delete icon. . . that’d be a bad thing.
  6. Within the wp-config.php file, change the DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST values to be that of the desired database. Save the file.
  7. Now reload your site – https://[your-site-name].azurewebsites.net. This should load the default WordPress installation page prompting you to complete the WordPress installation.
  8. Complete the installation. This should use the database setting as configured in the wp-config.php file to finish the WordPress installation.
  9. If you created a free ClearDB database to start with, feel free to delete that ClearDB database.

The Alternative

And now the real fun begins! In this alternative approach, an Azure Resource Manager (ARM) template can be used to create the WordPress site on Azure Web Apps and wire up a database of your choosing. To make this happen you will need the ARM template and a MySQL database of your choosing.

To get the ARM template, my first thought was that I could download the template that the Azure Portal is using and simply modify the database connection details to be what I wanted. Wrong. The templates I tried turned out to be a bit more complicated that I wanted. However, they did provide a good start and helped me understand what I needed to do.

If you’re curious, you can get the templates by invoking the PowerShell script below.

  # Retrieve all available items

 $allGalleryItems = Invoke-WebRequest -Uri "https://gallery.azure.com/Microsoft.Gallery/GalleryItems?api-version=2015-04-01&includePreview=true" | ConvertFrom-Json

 # Get all items published by WordPress

 $allGalleryItems | Where-Object { $_.PublisherDisplayName -eq "WordPress" } 

 $allGalleryItems | Where-Object { $_.Identity -eq "WordPress.WordPress.1.0.0" } 

 # Save default template for all items under directory "C:\Templates" 

 $allGalleryItems | Foreach-Object
 { 

     $path = Join-Path -Path "C:\templates" -ChildPath $_.Identity

    New-Item -type Directory -Path $path -Force

 

     $.Artifacts | Where-Object { $.type -eq "template" } | ForEach-Object { 

         $templatePath = Join-Path -Path $path -ChildPath ( $_.Name + ".json" ) 

 

         (Invoke-WebRequest -Uri $_.Uri).Content | Out-File -FilePath $templatePath

     } 

 } 

 

(original PowerShell sample from https://github.com/Azure/azure-powershell/issues/1064)

Using the ARM template obtained from the gallery sample as inspiration, I created a new ARM template. You can get the full sample on my GitHub repo at https://github.com/mcollier/AzureWebApp-WordPress-AlternativeDatabase.

  "resources": [ 
     { 

       "apiVersion": "2014-06-01", 

       "name": "[parameters('hostingPlanName')]", 

       "type": "Microsoft.Web/serverfarms", 

       "location": "[resourceGroup().location]", 

       "tags": { 

         "displayName": "HostingPlan" 

       }, 

       "properties": { 

         "name": "[parameters('hostingPlanName')]", 

         "sku": "[parameters('sku')]", 

         "workerSize": "[parameters('workerSize')]", 

         "numberOfWorkers": 1

       } 

     }, 

     { 

       "apiVersion": "2014-06-01", 

       "name": "[variables('webSiteName')]", 

       "type": "Microsoft.Web/sites", 

       "location": "[resourceGroup().location]", 

       "tags": { 

         "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource", 

         "displayName": "Website" 

       }, 

       "dependsOn": [ 

         "[concat('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]" 

       ], 

       "properties": { 

         "name": "[variables('webSiteName')]", 

         "serverFarm": "[parameters('hostingPlanName')]" 

       }, 

       "resources": [ 

         { 

           "apiVersion": "2014-11-01", 

           "name": "connectionstrings", 

           "type": "config", 

           "dependsOn": [ 

             "[concat('Microsoft.Web/sites/', variables('webSiteName'))]" 

           ], 

           "properties": { 

             "defaultConnection": { 

               "value": "[variables('dbConnectionString')]", 

               "type": 0

             } 

           } 

         }, 

         { 

           "apiVersion": "2014-06-01", 

           "name": "web", 

           "type": "config", 

           "dependsOn": [ 

             "[concat('Microsoft.Web/sites/', variables('webSiteName'))]" 

           ], 

           "properties": { 

             "phpVersion": "5.6" 

           } 

         }, 

         { 

           "name": "MSDeploy", 

           "type": "extensions", 

           "location": "[resourceGroup().location]", 

           "apiVersion": "2014-06-01", 

           "dependsOn": [ 

             "[concat('Microsoft.Web/sites/', variables('webSiteName'))]", 

             "[concat('Microsoft.Web/Sites/', variables('webSiteName'), '/config/web')]" 

           ], 

           "tags": { 

             "displayName": "WordPressDeploy" 

           }, 

           "properties": { 

             "packageUri": "https://auxmktplceprod.blob.core.windows.net/packages/wordpress-4.3.1-IIS.zip", 

             "dbType": "MySQL", 

             "connectionString": "[variables('dbConnectionString')]", 

             "setParameters": { 

               "AppPath": "[variables('webSiteName')]", 

               "DbServer": "[parameters('databaseServerName')]", 

               "DbName": "[parameters('databaseName')]", 

               "DbUsername": "[parameters('databaseUsername')]", 

               "DbPassword": "[parameters('databasePassword')]", 

               "DbAdminUsername": "[parameters('databaseUsername')]", 

               "DbAdminPassword": "[parameters('databasePassword')]" 

             } 

           } 

         } 

       ] 

     } 

 

The most relevant section is the MSDeploy resource extension.  It is this extension that deploys WordPress and gets the default database connection string set up. You provide the database server name, database name, database username and database password as input parameters to the ARM template. The ARM template will use those parameters to construct a database connection string in the proper format (set in a variable in the template).

Once the template is created, it can be deployed with a few lines of PowerShell:

 
 $ResourceGroupName = "wordpress-001" 

 $ResourceGroupLocation = "East US" 

 $TemplateFile = "azuredeploy.json" 

 $TemplateParametersFile = "azuredeploy.parameters.json" 

 

 # Create or update the resource group using the specified
template file and template parameters file

New-AzureRmResourceGroup -Name $ResourceGroupName
 -Location $ResourceGroupLocation
 -Verbose -Force
 -ErrorAction Stop

 

New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem
 $TemplateFile).BaseName
 + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 

                                    -ResourceGroupName $ResourceGroupName ` 

                                     -TemplateFile $TemplateFile ` 

                                    -TemplateParameterFile $TemplateParametersFile ` 

                                    -Force -Verbose

 

The reason I like this approach is that it is very clear what is being deployed. I can customize the template however I like, adding or removing additional resources as needed. Plus, I don’t have to go through that "create a database just to delete it" dance.

For instance, I can envision a version of this ARM template that may optionally set up a MySQL database on an Azure VM. Oh . . . look here, https://azure.microsoft.com/en-us/documentation/templates/wordpress-mysql-replication/.  Someone already did mostly just that! That template could be modified to have some options to allow for the creation of a database in a few different configurations. Thanks for saving me some work. Naturally I found this after I went through all the work above. Go figure!