Wordpress development with Docker
Tags: docker, dockercompose, container, wordpress
Published: 2018-03-26 20:01:09

---
title: Wordpress development with Docker
published: true
tags: docker,docker-compose,container,wordpress
canonical_url: 
---

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.

<amp-img on="tap:lightbox" role="button" tabindex="0" width="792" height="269" layout="responsive" src="/assets/img/1_7a8Qffxkg7WuePBZUebYSw.png"></amp-img>
* * *

# 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`](https://hub.docker.com/r/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.
<amp-img on="tap:lightbox" role="button" tabindex="0" width="600" height="630" layout="responsive" src="/assets/img/600px-install-step5.png"></amp-img>

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.

<amp-img on="tap:lightbox" role="button" tabindex="0" width="1698" height="1058" layout="responsive" src="/assets/img/wordpress-docker-folder-structure.png"></amp-img>

`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.](https://gist.github.com/netcell/fbbd178b027ad411c8082a606f530718#file-dockerfile)**

## 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`.

<amp-img on="tap:lightbox" role="button" tabindex="0" width="1768" height="726" layout="responsive" src="/assets/img/wordpress-docker-diagram.png"></amp-img>

## 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.](https://gist.github.com/netcell/fbbd178b027ad411c8082a606f530718#file-docker-entrypoint-sh)**

It’s time to modify `docker-entrypoint.sh`. Starting with a clone of the script from [`chriszarate/wordpress`](https://hub.docker.com/r/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
```

<amp-img on="tap:lightbox" role="button" tabindex="0" width="673" height="540" layout="responsive" src="/assets/img/permission-granted.gif"></amp-img>

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](https://github.com/chriszarate/docker-compose-wordpress/blob/master/docker-compose.yml). 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.