A Docker solution for Drupal 8 on Azure Web App for Containers.
In September 2017 Microsoft announced the general availability of Azure Web App for Containers and Azure App Service on Linux.
While it is possible to host Drupal websites with Azure App Service on Linux, its built-in image for PHP is not an ideal environment for Drupal in production. At SNP we turned our attention to the Web App for Containers resource as a way to provide custom Docker images for our customers. Our priorities were to:
- Include Drupal code in the image, not referenced from the Web App /home mount.
- Set custom permissions on the document root.
- Add Drush (the Drupal CLI)
- Add more PHP extensions commonly used by Drupal 8 sites
- Add additional PHP configuration settings recommended for Drupal 8
This repository is an example solution for Drupal 8. By itself, this solution does not install Drupal. You need to bring your own code and database. (More about this below.)
This repository is intended to satisfy common Drupal 8 use cases. We expect that users of this solution will customize it to varying degrees to match their application requirements. For instance, we include many PHP extensions commonly required by Drupal 8, but you may need to add one or more (or remove ones that you do not need).
When originally committed as a public repository on GitHub, the solution was very different - the base image was nginx; php 7.0 was installed in the container upon build; and Composer was included. The original deployment has been archived as branch nginx-php7.0.
While considering an upgrade to php 7.3, we decided to take a different approach, more consistent with our Drupal 7 on App Service solution and the official Docker Hub image for Drupal. You'll find in this latest version:
- Base image is
php:7.3-apache-stretch - Rsyslog support
- ARM Template for your Web App for Containers resources that you can include in a release pipeline.
In the Dockerfile, there is a placeholder for your code:
RUN git clone -b $BRANCH https://$GIT_TOKEN@github.com/$GIT_REPO.git .
Note the use of build args for $BRANCH, $GIT_TOKEN, and $GIT_REPO.
Alternatively, you can use the Docker COPY command to copy code from your local disk into the image.
Our recommendation is to place your code in a directory directly off the root of the repository. In this repository we provide a /docroot directory into which you can place your application code. In the Dockerfile, it is assumed that the application code is in the /docroot directory. Feel free, of course, to rename the directory with your preferred naming convention.
⚠️ If you use a different directory as your document root, remember to change theDocumentRootvalue inapache2.conf.
MySQL (or other Drupal compatible database) is not included in the Dockerfile. You can add this to the Dockerfile, connect to another container hosting MySQL, or utilize an external database resource such as Azure Database for MySQL.
Azure Web App provides a setting into which you can enter a database connection string. This string is an environment variable within the Web App. At run-time, this environment variable can be interpreted in your settings.php file and parsed to populate your $databases array. However, in a container SSH session, the environment variable is not available. As a result Drush commands do not work if they require a database bootstrap level.
An alternative to the Web App, connection string environment variable is to reference in settings.php a secrets file mounted to the Web App /home directory. For example, assume that we have a secrets.txt file that contains the string:
db=[mydb]&dbuser=[mydbuser]@[myazurewebappname]&dbpw=[mydbpassword]&dbhost=[mydb]
In our settings.php file, we can use the following code to populate the $databases array:
$secret = file_get_contents('/home/secrets.txt');
$secret = trim($secret);
$dbconnstring = parse_str($secret,$output);
$databases = array (
'default' =>
array (
'default' =>
array (
'database' => $output['db'],
'username' => $output['dbuser'],
'password' => $output['dbpw'],
'host' => $output['dbhost'],
'port' => '3306',
'driver' => 'mysql',
'prefix' => false,
),
),
);
⚠️ Web App for Containers mounts an SMB share to the/homedirectory. This is provided by setting theWEBSITES_ENABLE_APP_SERVICE_STORAGEapp value totrue.If the
WEBSITES_ENABLE_APP_SERVICE_STORAGEsetting isfalse, the/homedirectory will not be shared across scale instances, and files that are written there will not be persisted across restarts.
To persist files, we leverage the Web App's /home directory that is mounted to Azure File Storage. The /home directory is accessible from the container. As such, we persist files by making directories for /files and /files/private and then setting symbolic links in our Dockerfile, as follows:
# Add directories for public and private files
RUN mkdir -p /home/site/wwwroot/sites/default/files \
&& mkdir -p /home/site/wwwroot/sites/default/files/private \
&& ln -s /home/site/wwwroot/sites/default/files /var/www/html/docroot/sites/default/files \
&& ln -s /home/site/wwwroot/sites/default/files/private /var/www/html/docroot/sites/default/files/private
Similarly we persist log files such as php-error.log and drupal.log
...
&& mkdir -p /home/LogFiles \
&& ln -s /home/LogFiles /var/log/apache2
You can also use the /home mount for settings.php configurations that use files outside the repo. For example:
* Location of the site configuration files.
settings['config_sync_directory'] = '/home';
/**
* Salt for one-time login links, cancel links, form tokens, etc.
*
* Include your salt value in a salt.txt file and reference it with:
*/
$settings['hash_salt'] = file_get_contents('/home/salt.txt');
-
Log in as an administrator and clear all caches. You can do this from the
/admin/config/development/performancepage, or via the drush commanddrush crin an SSH session via Kudu. Clear your browser cache and reload your home page. If the theme still does not load, try visiting the site from a different browser. -
If these steps fail to solve the problem, proceed with the tips below.
-
Check your php-error.log. Perhaps there is a glaring problem.
-
Verify that your website code is in the
/var/www/html/docrootdirectory. Start an SSH session via Kudu, change to your/var/www/html/docrootdirectory, list directory contents. It should resemble this:
root@356db9bf9fdf:/home# cd /var/www/html/docroot/
root@356db9bf9fdf:/var/www/html/docroot#ls
INSTALL.txt composer.json index.php sites web.config LICENSE.txt composer.lock modules themes README.txt core profiles update.php autoload.php example.gitignore robots.txt vendor
- While in the
/var/www/html/docrootdirectory, check your site status with drush. It should resemble this:
root@356db9bf9fdf:/var/www/html/docroot# drush status
Drupal version : 8.8.5
Site URI : http://default
Database driver : mysql
Database hostname : mydatabase.mysql.database.azure.com
Database port : 3306
Database username : user@mydatabase
Database name : mydbname
Database : Connected
Drupal bootstrap : Successful
Drupal user :
Default theme : umami
Administration theme : seven
PHP configuration : /usr/local/etc/php/php.ini
PHP OS : Linux
Drush script : /usr/local/bin/drush
Drush version : 8.1.13
Drush temp directory : /tmp
Drush configuration :
Drush alias files :
Install profile : demo_umami
Drupal root : /var/www/html/docroot
Drupal Settings File : sites/default/settings.php
Site path : sites/default
File directory path : sites/default/files
Temporary file directory path : /tmp
Sync config path : /home
- While in the
/var/www/html/docrootdirectory, rebuild your cache withdrush cache-rebuildordrush cr.
-
If you are using an Azure Database for MySQL resource, ensure that you have a Firewall rule configured in the resource's Connection Security settings.
-
Validate that you can connect to the database from container. Start an SSH session via Kudu and from the
/homedirectory (or any directory for that matter) upload a simple php file to check your connection string. Here is an example of a file namedphp-pdo-test.phpuploaded to the/homedirectory.
<?php
$secret = file_get_contents('/home/secrets.txt');
$secret = trim($secret);
$dbconnstring = parse_str($secret,$output);
$database = $output['db'];
$username = $output['dbuser'];
$password = $output['dbpw'];
$dbhost = $output['dbhost'];
$dsn = 'mysql:dbname=' . $database .';host=' . $dbhost;
try {
$dbh = new PDO($dsn, $username, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
$sql = 'SELECT u.`uid`, u.`uuid` FROM `users` AS u';
foreach ($dbh->query($sql) as $row) {
echo $row['uid'] . "\t";
echo $row['uuid'] . "\n";
}
?>
Run file from the command line. Your output should resemble:
root@356db9bf9fdf:/home# php php-pdo-test.php
3 2dad0fee-2d31-4acf-951f-acb816a0e673
5 6d60461c-81f8-4583-9bbd-47d130e8abac
1 7b07bdc7-fe74-4a99-bc7a-e9aae49fc46e
0 7d730abc-a986-4a72-9a3e-62eb8805e68f
7 956730d4-23d8-4c2e-a755-daa9146433d4
4 aad4b8a9-6a7d-4063-b0ec-ff3e3a1237ee
2 ab1ddaaf-2472-4634-b390-8904dd16ab73
6 fe18b850-7149-435e-b6cf-3b6166b8ff86
- Web App for Containers home page
- Use a custom Docker image for Web App for Containers
- Understanding the Azure App Service file system
- Azure App Service on Linux FAQ
- Things You Should Know: Web Apps and Linux
- Docker Hub Official Repository for php
Git repository sponsored by SNP Technologies
If you are interested in a WordPress container solution, please visit https://github.com/snp-technologies/Azure-App-Service-WordPress.