One of my obsessions lately is keeping my operating system as clean as possible. In order to do that, I perfer to install as few as possible anything on it.
Node.js makes that quest so easy as I only need node
installed globally and everything else can be installed locally inside project directory. However, for many other languages, such as Python
and PHP
, it’s often not the case. Furthermore, when each project requires different environemnt setup, it quickly becomes a painful job managing everything and sometime you break the whole system.
Docker to the rescue.
While mostly used for controlling production and teamwork environments, Docker is also very useful for encapsulating development environment and providing a “clean slate” to make sure nothing breaks unintentionally.
Goals
When building a docker development system, I often consider the following requirements:
- Minimum volume mounting: File IO operations on mounted host volumes are slow (on Windows and Mac) so you would only want to mount the very sourcecode files you are going to edit. Others should be copied when builing the image.
- No runtime installation: Dependencies and 3rd-party plugins should not be installed at container run time, especially if they require internet connection, to ensure fastest booting time when you resume the development. They should be installed when building the image.
-
Minimum 3rd-party on host: If possible, dependencies and 3rd-party plugins should be fetched from the internet (via a configuration file like
package.json
orGemfile
or even listed insideDockerfile
). Otherwise they should be kept as zip files and copied/extracted when building the image. There are two reasons for this:- Copying a big amount of files from host to image is very slow.
- Keeping a big amount of 3rd-party files in your git directory is messy.
- Trackable snapshot: You should be able to take snapshots of your current development progress (especially in Wordpress case as explained below), and the they should be able to be tracked in git repository.
Base Docker Image
For Wordpress development, I use chriszarate/wordpress
since it provides a bunch of useful features via envinronment varaibles including:
- Automatically activate plugins when container starts. Because no one wants to activate theme manually every single time. However, this becomes irrelevant once you start using database snapshots.
- Automatically activate a theme when container starts. Similarly, this becomes irrelevant once you start using database snapshots.
- Append additional PHP to
wp-config.php
. - Preconfigure admin user, admin password, admin email and site url to skip the boring welcome setup screen.
The magic behind this docker image is a set of wp-cli
scripts appended into the default entrypoint docker-entrypoint.sh
of the official wordpress
docker image.
Folder Structure
Here is the folder structure of my Wordpress development directory, there are 3 main sections (which are poorly named in the screenshot but explained nicely in the picture below):
-
Sourcecodes: The plugins and themes you are currently developing. They are text files (
.php
,.css
,.xml
, etc.) that you are going to create and change during most of your project timeline. -
3rd-parties: They are the 3rd-party plugins that your site needs, the dependencies of your project or the parent theme for which you are creating child theme. They should be kept as zip files in these folders or specified as urls in
Dockerfile
. -
Snapshots: Including the sql dump from wordpress database and the compressed file of
wp-content/uploads
folder (in case you are building contents or starting with demo content of a theme).
With this structure, 3rd-parties and Snapshots will be built into the image, while sourcecodes are mounted to allow changes in development.
docker-entrypoint.sh
is the default entrypoint docker-entrypoint.sh
of the original docker image and will be modified (see below) to fit our needs.
Dockerfile
For a sample
Dockerfile
, checkout this gist.
Packages
For Dockerfile configuration, I install the following packages:
-
unzip
: As noted above, snapshots and 3rd-parties are saved as zip files (and in the case of remotely fetched, also in zip format). We would need this utility to decompress them. -
mysql-client
: This package provides neccessary commands required bywp db
to export and import wordpress database as.sql
snapshot. -
[Optional]
wget
: In the case where dependencires are remotely fetched, we usewget
to download them from specified urls.
FROM chriszarate/wordpress:4.9.1
RUN \
apt-get update && \
apt-get install unzip wget mysql-client -y && \
rm -rf /var/lib/apt/lists/*
3rd-parties and Snapshots
We copy each 3rd-party directory of zip files to a temporary folder on the image (or download via wget
), extract and delete all zip files. The content of these folders will be copied to real target directories at runtime by docker-entrypoint.sh
.
The reason we are not copying them directly to the real target folders is that they would be overwritten by commands in official wordpress image’s
docker-entrypoint.sh
.
# If copied from folder
COPY ./plugins/ /temp/plugins
# If downloaded via url
wget -P /temp/plugins/ https://downloads.wordpress.org/plugin/jetpack.5.9.zip
# Extract and delete zip files
RUN unzip '/temp/plugins/*.zip' -d /temp/plugins && rm /temp/plugins/*.zip || true;
|| true
is added to make sure that when there are no zip file the script would not fail and terminate image build process.
Similarly we copy snapshot files (if available) to the image. The uploads.zip
file should be decompressed like above, while wordpress.sql
would later be processed by docker-entrypoint.sh
.
Entrypoint and Configurations
We need to replace the default docker-entrypoint.sh
with our modified script. We don’t have to specify ENTRYPOINT
since it is already declared in the official docker image.
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
It is also useful to have your own php uploads configuration as uploads.ini
in your repository and insert into the image. Obviously you can config other internal system files using the same method: copy a custom file to the absolute path inside the image.
COPY ./uploads.ini /usr/local/etc/php/conf.d/uploads.ini
Entrypoint Script
For a sample
docker-entrypoint.sh
, checkout this gist.
It’s time to modify docker-entrypoint.sh
. Starting with a clone of the script from chriszarate/wordpress
, we would need to copy all files in /temp/
directory to where they need to be. These lines need to be placed before plugins and themes activation.
CONTENT_DIR=$ROOT_DIR/wp-content
THEME_DIR=$CONTENT_DIR/themes
PLUGIN_DIR=$CONTENT_DIR/plugins
cp -r /temp/themes/* $THEME_DIR || true
cp -r /temp/plugins/* $PLUGIN_DIR || true
cp -r /temp/base/* $CONTENT_DIR || true
# Activate plugins. Install if it cannot be found locally.
# ...
One thing to notice here is that, all these files and folders are owned by root
user, hence will not be writable by Wordpress. If you need to give Wordpress permission over these directories, you need to insert something like this:
chown -R $WEB_USER:$WEB_USER $ROOT_DIR/wp-content/uploads
Finally, when everything is done, at the end of docker-entrypoint.sh
, we would want to import wordpress.sql
file. We place this line at the end of the file, just before exec "$@"
, to ensure our database would not be overwritten by anything.
runuser $WEB_USER -s /bin/sh -c "wp db import $CONTENT_DIR/wordpress.sql"
exec "$@"
Docker Compose
For docker-compose
configuration, you can find an example at chriszarate’s repository. It’s a combination of 2 services: wordpress
for PHP wordpress installation and mysql
for database. There are only a few things to change here:
-
Build your Dockerfile: instead of using
chriszarate/wordpress
image directly
services:
wordpress:
build: .
-
Mount your sourcecodes according to your project, one by one. Do not mount the entire
themes
orplugins
directory since their versions inside the container are already populated with other files.
volumes:
- "./src/themes/my-custom-theme:/var/www/html/wp-content/themes/my-custom-theme"
- "./src/plugins/my-custom-plugin:/var/www/html/wp-content/plugins/my-custom-plugin"
-
Local only: Only list plugins and themes that are installed inside docker image on
WORDPRESS_ACTIVATE_PLUGINS
andWORDPRESS_ACTIVATE_THEME
environment variables. Otherwise, the entrypoint script will attempt to download them from the internet and prolong the startup time.
Now we are set. Let’s go and clean off any XAMPP or MAMP stack, or just delete everything and reinstall your operating system, or even better throw your machine away for a brand new computer. Have fun.
Top comments (5)
Built something that lets you use this set-up without installing or running Docker on your local machine. dev.to/porter/wordpress-developmen...
It runs these containers in a remote machine (in a kubernetes cluster) and lets you develop directly inside them by syncing your local files with the remote files. Would love to hear your feedback :)
This is pretty much what I'm providing out the box with Takeoff (takeoff.sh) + Wordpress Blueprint (github.com/takeoff-env/takeoff-blu...) although I must admit I need to fix a couple of things since I haven't needed to use it since I created it.
The blueprint comes out the box with Ngnix + PHP + MySQL setup, and it uses git submodules to clone Wordpress from it's repo to the local volume - it then uses some conventions to set up and run it from the local files within the Docker environment.
Would love you hear your opinion on using it
Thank you, I'll check it out.
Checking your wordpress blueprint I figure you are mounting the entire wordpress directory. My main point in this post is to mounting a minimum amount of files, especially with plugins/themes that is not related to the project and not gonna be modified.
Btw, I didn't know my posts are missing images. Time to fix my site :(
Yea, I'm thinking to update the blueprint to do this instead, it was a good takeaway from this article. I also want to provide a more generic PHP blueprint too to get people started, so possibly make the Wordpress install optional via scripts.
Hi
In the image /chriszarate/wordpress/ , it seems Xdebug is activated by default no?
Bad for performances no ?