I have used Vagrant for local WordPress development and decided to move to Docker. However, configuring Docker is painful. I didn’t understand how containers work together, how to configure existing images, and etc. I wanted to write down my process to figure the Docker configurations. This instruction is based on the Dennis Andersson‘s post: www.brandson.se.
Installing Docker is quite easy in this day. Just visit the Docker website and follow the installation guide.
Create the base Docker containers
Save the file below on your host machine* and execute the command on a terminal from the same directory you saved the file.
* Host machine means your desktop or laptop. I was sick with poor explanation like ‘set the port number’: which port number? host? or container? or whatever else? I will use this term below to prevent misunderstanding.
docker network create local_default
docker-compose up -d
Docker is a game changer, but it supports only command line, so it’s hard to memorize. Docker composer is a tool for downloading images and creating containers automatically with
.yml blueprint. If you created MariaDB with command line, it would be like this.
docker run --name mariadb -e MYSQL_ROOT_PASSWORD= password -p 3306:3306 --restart always -v mysql:/var/lib/mysql -d mariadb
.yml file above creates MariaDB, NGINX proxy server, MailHog, and Portainer containers, and you can create this at any time with saved
.yml. The command
docker-compose up -d means booting up the composer with default configuration file (
docker-compose.yml) with the option
-d, which is the detached mode — run containers in the background, print new container names.
When you execute the docker-composer up command, it will download the server images and make containers. Once your host machine downloads the image, creating a new container with the image would be much faster.
Container and Image
Docker image is read-only source. Docker image itself doesn’t work and isn’t executable. The image is like
.dmg disk image file. When you mount the image into your host machine, the container will be created. The container is changeable. For example, the MariaDB image contains the database, but it is not a source file. It is ready to use. When you insert this image into your host, it makes a container. You can create more than one containers from same image, and making many containers from one image does not create the clone of the image. The container just saves changes related on the container. When you insert some records into your MariaDB container A, it will be saved into the container A, not an image.
docker-compose.yml is doing
Well, let’s dig in how the
.dmg and composer working. Under the
services:, there’re four servers. First one is MariaDB. Let’s look at the line 4 to 14.
container_name: mariadb, obviously the name of the container. Without this option, Docker composer names it as
dirname_imagename_1. I personally like clear naming.
The second one is
image: mariadb. It will find the image from your host machine and Docker repository — Docker Hub. You don’t have any MariaDB image on your host machine, so it will download the image from the Docker Hub.
The third one is
environment:. This can set environment variables that the image supports. You can find what you can set on the Docker Hub page — https://hub.docker.com/_/mariadb/.
Next one is a
volumes. This setting is the most important for beginners. Volumes option connects files and directories between your host machine and the container. Remember that the container keeps the changes that you make to an image. That means if you delete the container, then your records/tables/databases are deleted along with it.
Forever. Doomsday! Got fired. If you upgrade the database version — downloading a new image and create a new container, and replace the existing container –, it will delete the data as well. So, you should keep your data on the host machine.
- mysql(host location):/var/lib/mysql(container location) creates and connects the database files on your host machine. This volume,
mysql, is declared on line 45. Docker will creates the volume named
mysql in the default location (for Mac, it’s on the deeply-hidden location
/var/lib/docker/volumes). If you specify the location like
- ~/mysql:/var/lib/mysql, you’ll see the
~/mysql directory on your home directory. With this setting, you can keep the MySQL data safe.
ports: - 3306:3306 setting is similar with
volumes. It connects between the host machine’s port and container ports. When you calls the 3306 port (first), it calls the container’s 3306 port(last). If you run several websites on your host machine for development, you can separate ports number like site 1 is localhost:8080 (
- 8080:80), and site 2 is localhost:8081 (
- 8081:80). Now, host machine’s 3306 port points the container, so you can call the database from your host machine. For example, my beloved MySQL GUI tool, Sequel Pro.
restart option makes the container restarts automatically when it stopped, and the
network defines the network of container working. The network should be the same with WordPress container’s network.
The Containers: NGINX Proxy
The proxy server allows using the virtual host for the other web server containers. With the NGINX proxy, you can access the other WordPress containers with a host name (http://wordpress.test) not a port number (http://localhost:8080). The
command option run the shell script to increase max body size for increasing upload size.
The Containers: MailHog
The MailHog captures emails from your web servers. You can access here by typing http://localhost:8025 on your browser.
The Containers: Portainer
Portainer provides the web frontend for managing the Docker. This is quite useful to see missing, unused images, networks, and volumes. You can access here by typing http://localhost:9000 on your browser.
Create the WordPress Image
The official WordPress image is
wordpress. You can download image and create container with an option
image: wordpress:latest or
image: wordpress:php7.2 (See the Docker Hub for more information). The problem for the official image is that has nothing on it. It is minimized configured version of WordPress and web server, and it is suitable for live server. As a developer, I wanted set something more. For example, xDebug is not installed. I can install it by accessing the container with a command line, but I should install that for every WordPress containers that I created. I decided to make a new WordPress image for development.
To create a new image, we should make a text file named
Dockerfile and execute this command.
docker build -t sujin2f/wordpress .
docker build command… yes builds a new image on your host machine.
-t option is tag, so your image’s name will be
sujin2f/wordress with a tag,
:latest. In the
Dockerfile, we can find which is the base image (
wordpress:php7.2 in this case) and Docker will execute several commands for the new image based on the
Dockerfile. Consequently, your new image contains xDebug, MailHog support, PHP ZipArchive module, and increased upload size on
Create the WordPress Container
It’s the final step! Let’s create the WordPress container with the image you created. Save this file on your project root and rename it
docker-compose.yml. I prefer to create
wp-content directory for each project when I develop a whole website. Depends on your situation, the project root could be a theme or a plugin. Let’s make the container!
docker-compose up -d
container_name: as you want, and make the
image: same as the image name you created. If you don’t know the image name, check the Portainer.
external_links: option connects your MariaDB container and MailHog container to the WordPress container. On the
environment: setting, set the
VIRTUAL_HOST any hostname you want. I prefer
.test domain because Google Chrome redirects our beloved domain
https://. I don’t want to set many things for development. If you need to set SSL on the development level, put
- 443:443 on the NGINX proxy port and
- 443 on the WordPress port, and create SSL certificates
, and blah blah blah...
WORDPRESS_DB_NAME, and Docker will create the database on MariaDB.
WORDPRESS_DB_PASSWORD are the similar. Docker will create the database user. If you like to enable
true, put the
volumes: option would be
- ./:/var/www/html/wp-content if you want your project root as a
wp-content. You can change it like
- ./plugins/your-plugin:/var/www/html/wp-content/plugins/your-plugin depends on your situation.
ports: would be
- 80. Docker will make a random port for the container. It’s like
All is done. You can connect to the WordPress container with http://localhost:xxxx (the random port number/ Check it on Portainer). Something is weird. Where is my
.test domain? Well, if you put the host setting on your host machine, you can use it. Run
sudo vim /etc/hosts and put
127.0.0.1 wordpress.test. Yea! Your request will call NGINX proxy, and the proxy will call your WordPress container. You are all set! In this step, you can create the super fast and light WordPress container as many as you want.
One last step: dnsmasq
If you are so lazy like me, you might not want to type
sudo vim /etc/hosts whenever you add a new website. You can make your host machine to resolve a specific domain to a specific IP address. Follow this step.
- Install Homebrew
- Install dnsmasq via Homebrew
brew install dnsmasq
- Make the dnsmasq configuration to resolve
.testrequest to 127.0.0.1
mkdir -pv $(brew --prefix)/etc/
echo 'address=/.test/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
- Register LaunchDaemons so you wouldn’t run this again
sudo cp -v $(brew --prefix dnsmasq)/homebrew.mxcl.dnsmasq.plist /Library/LaunchDaemons
sudo launchctl load -w /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
- Create resolver for
.testdomain and set the name server
sudo mkdir -v /etc/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/test'
If you are really hurry, kill and restart dnsmasq.
sudo kill -9 $(pgrep dnsmasq)
All done. Enjoy Docker!
Note: You may recognized how to set the Portainer, MailHog, and MariaDB address to http://portainer.test or http://mailhog.test