Docker-izing WordPress for Kubernetes
WordPress is amazingly popular considering how antiquated the file structure and code appears to be. Even still, it is the easiest CMS that I have used and the community has created plugins to make the copy-folder-for-a-new-theme/plugin at least tolerable. A challenge comes when one wants to use the 1990’s method of serving web applications in a more modern way (such as running inside a container on top of Kubernetes).
Containers are meant to be immutable and treated as read-only. (No change to files in the container after they are built.) Containers are supposed to be a point-in-time release of software. As such, I can roll-back to a specific container version and have that specific code running. This causes a problem when one wants to use a file-dependent application such as WordPress. The best I could come up with for running WordPress in a container is a forward-only method of deploying code (basically, giving up the ability to use a previous version of code.) There is a way to keep that functionality, but it would mean storing everything (including uploads) inside an ever-growing container or using a central object store such as S3 for uploads. It would also require a re-build of the container every time a plugin is updated – which would presumably be every hour.
My deployments of WordPress are so little that I can hardly justify using S3 for uploads, keeping the plugins in sync, and going backwards in time.
When deploying to Kubernetes, one can scale the replicas to N copies. Keeping plugins, themes, and updates the same across all replicas will require a READ WRITE MANY (rwx) volume to be shared. This could be a GlusterFS volume or NFS, but it cannot be a AWS EBS volume or any other single-use block storage.
When looking at the available WordPress images, there are three that seem interesting. With the official image, I like that I can use php-fpm and alpine. The next top two implementations of WordPress have very bloated docker files. I have come to the conclusion that my WordPress container will have to be built from scratch.
The Dockerfile is very similar to the official WordPress container. It uses
php:7.2-fpm-alpine
as the base image, adds in nginx, and inserts a generic wp-config.php file.
The folder structure for the container is as follows:
nginx.conf is a very basic configuration file with gzip and cache headers. The real neat things come in the docker-entrypoint.sh file.
WordPress Container Folder
├── docker-entrypoint.sh
├── Dockerfile
├── html
│ └── ... Contents of wordpress-X.Y.Z.zip
├── nginx.conf
└── wp-config.php
It can be built by running a command similar to docker build -t andrewwippler/wordpress:latest .
I borrowed the database creation script; however, since PHP was already installed in the container, I ran a few more checks in PHP rather than bash.
For instance, the container places the local code in
All files I have referenced in this article are located in a gist. In addition to those files a local docker-compose.yml file might be helpful for your local development:
/var/www/html-original
and rsyncs it to /var/www/html
where the webserver sees it, but it only does this if the code in html-original
is newer than html
. This allows an operator to mount a storage volume at /var/www/html
which can be shared across Kubernetes Deployment replicas. The code for this is:
// see if we need to copy files over
include '/var/www/html-original/wp-includes/version.php';
$dockerWPversion = $wp_version;
if (file_exists('/var/www/html/wp-includes/version.php')) {
include '/var/www/html/wp-includes/version.php';
$installedWPversion = $wp_version;
} else {
$installedWPversion = '0.0.0';
}
fwrite($stderr, "dockerWPversion: $dockerWPversion - installedWPversion: $installedWPversion\n");
if(version_compare($dockerWPversion, $installedWPversion, '>')) {
fwrite($stderr, "Installing wordpress files\n");
exec('rsync -au /var/www/html-original/ /var/www/html');
}
I have also included a theme-only check that will update the theme if it has changed. This is necessary to update the theme files when the version of WordPress has not changed.
if (filemtime('/var/www/html-original/wp-content/themes') > filemtime('/var/www/html/wp-content/themes')) {
fwrite($stderr, "Updating theme files\n");
exec('rsync -au --delete-after /var/www/html-original/wp-content/themes/ /var/www/html/wp-content/themes');
}
version: '2'
services:
db:
image: mariadb:10
volumes:
- ./tmp/db:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=secretPASS
wordpress:
build: wordpress
volumes:
- ./html:/var/www/html
- ./nginx.conf:/etc/nginx/nginx.conf:ro
links:
- db
environment:
- WORDPRESS_DB_NAME=wordpress
- WORDPRESS_DB_HOST=db
- WORDPRESS_DB_USER=root
- WORDPRESS_DB_PASSWORD=secretPASS
ports:
- 8080:80