I had looked into Docker in the past, but given how easy Vagrant and VVV were, I was in no hurry to switch. VVV still is awesome (there is an excellent getting started guide here), but since settling on Vagrant, the tools and ecosystem around Docker have significantly improved.
Mainly to satisfy my own curiosity, but also hoping to use less system resources (since I currently code on a Macbook Air) I finally gave Docker a fair chance. I wanted to share some of the things I’ve learned over the past week whilst switching.
Docker and Vagrant differences
Vagrant uses boxes (virtual machines) to power your webserver. These boxes run operating systems of their own in isolation from the host machine.
Docker on the other hand shares some resources with the host machine, and creates virtual environments called containers.
There are pros and cons to both systems, but I don’t want to delve in to that. Both work well. Instead I just want to show how things work in Docker vs Vagrant.
|Software requirements||Vagrant, VM Software such as VirtualBox||Docker engine, Docker desktop (optional)|
|Environments||Vagrant Boxes||Docker Images|
|Provisioning a machine||Use VVV or define a custom Vagrantfile||Run a predefined image, create a docker-compose.yml file, or use WP Local Docker.|
|Starting a machine|
|Stopping a machine|
|Destroy a machine|
|Accessing the command line of your environment|
Both are fairly similar in usage. Docker Desktop however does provide a nice GUI to see running containers which is nice.
Setting up Docker
Your first task is to install Docker. If you’re on a Mac, it’s as simple as installing Docker Desktop. As well as access to Docker on the command line, this also gives you a GUI to view, stop, run, or destroy your Docker containers.
Creating a WordPress container
docker run -p 8088:80 wordpress
While this is running on the command line you can access this WordPress install by pointing your browser at
https://localhost:8088. Stop it using control+c.
You can also run this image in “detached” mode by appending the
-d flag – to stop it you can use Docker desktop, or find its container ID and run the stop command.
Creating instances of WordPress with
You can create a file named
docker-compose.yml to define what containers should be setup for your project. This is documented here, but to give a basic WordPress example, take the following:
version: '3.3' services: db: image: mariadb:10.4 restart: always environment: MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress MYSQL_RANDOM_ROOT_PASSWORD: 'yes' volumes: - db:/var/lib/mysql wordpress: container_name: wordpress depends_on: - db image: wordpress:latest ports: - 8082:80 restart: on-failure environment: WORDPRESS_DB_HOST: db WORDPRESS_DB_NAME: wordpress WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_TABLE_PREFIX: wp_ WORDPRESS_DEBUG: 'true' volumes: - "./wp-content:/var/www/html/wp-content" - wordpress:/var/www/html volumes: db: wordpress:
This file does a few things:
- It tells Docker I want a database container running mariadb with the user login details I provide.
- It tells Docker I want a WordPress container, using the official WordPress image, which depends on the database container.
- It gives WordPress the database login details.
- It creates a shared volume on my host machine in the same path as my
docker-compose.ymlfile where I want to place
wp-contentfiles, such as themes and plugins.
In terminal, if I navigate to the directory holding this file, I can run
docker-compose start to start these containers, and then access the WordPress site at
http://localhost:8082. These containers will also be visible in Docker Desktop.
Using WP Local Docker to simplify the process
It handles the creation of containers, using versions of PHP you choose, and handles the setup of things such as Networking and your hosts file (which gives you nice URLs rather than localhost:xxxx).
Installation instructions can be found here. Once installed, you can spin up a local WordPress environment using the command
10updocker create and following the prompts:
This is the method I settled on using.
Sharing data with bind mounts
Containers by design have no access to the host machine. Therefore, if you are developing within a container (for example building WP plugins) you need a way to edit files.
Bind mounts, documented here, do just that, and allow you to share files between your host machine and a Docker container.
In my case, to keep my files organised I like to keep my local plugin git repositories together in a single directory. To allow my WordPress container to see those files I set up a bind mount.
If you edit the
docker-compose.yml file generated by WP Local Docker (this defaults to the
~/wp-local-docker-sites/<environment>/ directory) you’ll see two sections which look something like this:
volumes: - './wordpress:/var/www/html:cached'
These are the volumes and bind mounts. We can add to these to map more directories so that the container can access them:
volumes: - './wordpress:/var/www/html:cached' - '~/Sites/plugins:/var/www/html/wp-content/plugins' - '~/Sites/themes:/var/www/html/wp-content/themes'
After restarting the container, the container will be able to use and see any files in the local
You can of course be even more specific and set bind-mounts for specific plugins. For example, if I only wanted a bind mount for the WooCommerce plugin I could use:
volumes: - '~/Sites/plugins/woocommerce:/var/www/html/wp-content/plugins/woocommerce'
When using VVV, where you can see most of the files on the webserver locally by default, I set a similar system up using symlinks. Docker can be a little more tricky to grasp at first, but once you understand how containers work I think I prefer it.
Running commands in a Docker container
With VVV, if you needed to access the filesystem of a box you could use
vagrant ssh to tunnel in and gain access to the command prompt.
With Docker, you can do something similar with
docker exec which lets you execute commands on your container.
Want a bash prompt? Use
docker exec -it <container_name> /bin/bash. You can do the usual things such as exploring the filesystem and can
exit when you’re done.
This was the most challenging aspect for me when switching from VVV.
VVV comes preconfigured to run WordPress unit tests using the WP test suite. In the case of the plugins I work on daily, running tests involved SSHing in and running phpunit as follows:
cd /path/to/plugin/ .vendor/bin/phpunit
The WordPress Docker images only contain WordPress, so in order to run tests I needed to create a custom container with the WP Test Suite setup. I needed:
docker-compose.ymlfile like the example earlier with just a WordPress container and a container for the database.
- Rather than specifying the WordPress image, I created a custom Dockerfile. Dockerfiles let you execute commands when a container is built, and even let you utilise multiple containers during this process.
- A new NPM task to spawn the container and run PHPUnit automatically (so other developers can use this easily).
To use a DockerFile,
docker-compose.yml gets a section like this in the place of
build: context: ./tests/bin
docker-compose where to look for my Dockerfile. The Dockerfile itself can access anything in it’s directory, and in my case looked like this:
FROM wordpress:latest RUN apt-get update && apt-get -y install subversion unzip wget git-all CMD echo "Installing tests..." ENV WP_TESTS_DIR=/tmp/wordpress-tests-lib ENV WP_CORE_DIR=/usr/src/wordpress COPY install-wp-tests.sh /usr/local/bin/dockerInit RUN chmod +x /usr/local/bin/dockerInit RUN dockerInit wordpress wordpress wordpress db latest true # This is the entrypoint script from the WordPress Docker package. CMD ["docker-entrypoint.sh"] # Keep container active CMD ["apache2-foreground"]
The basic gist of this is:
- Using the WordPress image as a base, install some dependencies (SVN, GIT, and the unzip/gzip commands used elsewhere)
- Setup environment variables used by
install-wp-tests.shto the container (this bash script installs the test suite) and make it executable.
- Run the docker-entrypoint script (copied from the WordPress image) which sets up WordPress.
Now when this container is running, I can trigger PHPUnit to run with the following
docker exec -it --workdir /path/to/my/plugin <container_name> php ./vendor/bin/phpunit
You can see these changes in context in this pull request on GitHub which I raised to add a way to run unit tests on the WooCommerce Blocks plugin with Docker.
And that’s about it! I’ve only been on this setup a few days so it’s still early for a final judgement, but I’ve not hit any issues using Docker so far and it’s been fun to try something new. I hope this post is useful if you decide to try it!