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 or Gemfile or even listed inside Dockerfile). 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 by wp db to export and import wordpress database as .sql snapshot.
  • [Optional] wget: In the case where dependencires are remotely fetched, we use wget 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 or plugins 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 and WORDPRESS_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.

Loading comments

netcell

Software engineer with 4+ years of work experience in designing and developing cross-platform games and apps on web technology.