[Part 2/2] - WordPress and nginx with Docker Compose


In the previous video we got half way through setting up a Docker Compose WordPress development environment. In this video we are going to finish up our Dockerised WordPress development environment.

At this point we have our WordPress directory as follows:

tree -L 2 -a
.
├── Dockerfile
├── .env
├── Makefile
├── wordpress
│   ├── index.php
│   ├── license.txt
│   ├── readme.html
│   ├── wp-activate.php
│   ├── wp-admin
│   ├── wp-blog-header.php
│   ├── wp-comments-post.php
│   ├── wp-config-sample.php
│   ├── wp-content
│   ├── wp-cron.php
│   ├── wp-includes
│   ├── wp-links-opml.php
│   ├── wp-load.php
│   ├── wp-login.php
│   ├── wp-mail.php
│   ├── wp-settings.php
│   ├── wp-signup.php
│   ├── wp-trackback.php
│   └── xmlrpc.php
└── wp-config.php

Most of this is standard WordPress fare.

The additions of ours are the wp-config.php file in the directory root, along with the Makefile, Dockerfile, and .env file, as covered in the previous video.

Now, we also need a web server.

I'm going to use nginx.

Personally, I like to create a completely separate project for my nginx configurations.

Therefore, as I have all my WordPress stuff in /home/chris/Development/docker-wordpress-example.dev, I'm going to create a new directory:

/home/chris/Development/nginx.docker-wordpress-example.dev

This is somewhat misleading: nginx will not be a subdomain of my docker-wordpress-example.dev site. However, as I'm the one in control of my configurations, I understand what this means from a sysadmin / devops perspective, so it's good enough for me and my needs. Feel free to use a different convention, I won't judge you ;)

WordPress nginx Docker Example

Much like when we created an nginx Docker image for our Symfony project, the process here is to create some extra configuration files that we will copy into a new Docker image based on the official nginx Docker image.

mkdir /home/chris/Development/nginx.docker-wordpress-example.dev

cd /home/chris/Development/nginx.docker-wordpress-example.dev

# yes, this could have been a one liner, but ya' know, clarity...

Once inside our new directory we need to do some setup:

touch {Dockerfile,Makefile}
mkdir conf.d
touch conf.d/docker-wordpress-example.dev.conf
touch conf.d/upstream.conf

Which results in the following:

tree -L 2
.
├── conf.d
│   ├── docker-wordpress-example.dev.conf
│   └── upstream.conf
├── Dockerfile
└── Makefile

1 directory, 4 files

Ok, sweet, let's crack on.

We will start with the Dockerfile as it's the easiest thing:

FROM nginx:stable

EXPOSE 80
EXPOSE 443

COPY ./conf.d/upstream.conf /etc/nginx/conf.d/
COPY ./conf.d/docker-wordpress-example.dev.conf /etc/nginx/conf.d/

Nothing new here, if unsure on any of this, please watch this tutorial video.

Next, let's add the config files in. We will start with conf.d/upstream.conf:

upstream php-upstream {
  server php:9000;
}

Again, this was already covered.

The WordPress nginx site config is next (docker-wordpress-example.dev.conf):

server {
    listen 80 default;
    server_name docker-wordpress-example.dev;
    root /var/www/docker-wordpress-example.dev;

    index index.php index.html index.htm;

    location = /favicon.ico { log_not_found off; access_log off; }
    location = /robots.txt  { log_not_found off; access_log off; allow all; }
    location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
        expires max;
        log_not_found off;
    }

    location / {
      try_files $uri $uri/ /index.php?q=$uri&$args;
    }

    location ~ .php$ {
      try_files                  $uri =404;
      fastcgi_param              SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_pass               php-upstream;
      fastcgi_index              index.php;
      include                    fastcgi_params;
    }

    error_log /var/log/nginx/docker-wordpress-example.dev_error.log;
    access_log /var/log/nginx/docker-wordpress-example.dev_access.log;

    client_max_body_size 20M;
}

This is essentially the same config as found in the official WordPress docs.

The important point is that the path given in the root directive matches up to the same path we passed in to the docker build command in the previous video.

Again, as I am both lazy and forgetful, I like to use Makefile to save me from remembering any more of the Docker commands than strictly necessary:

docker_build:
    @docker build \
        -t codereviewvideos/nginx-wordpress .

docker_push:
    @docker push codereviewvideos/nginx-wordpress

bp: docker_build docker_push

This will allow me to run make bp, which will both make the Docker image from our Dockerfile and directory contents, and then push up to Docker Hub. Again, nothing new here.

Docker Compose WordPress

Ok, that's all the setup out of the way. Let's get Docker Compose to bring us an environment together.

I would add the docker-compose.yml file to my WordPress project root directory:

cd /home/chris/Development/docker-wordpress-example.dev
touch docker-compose.yml

And here's the contents:

version: '3'

services:

    db:
        image: mysql:5.7.20
        hostname: db
        volumes:
          - "./volumes/mysql:/var/lib/mysql"
        ports:
          - "33061:3306"
        env_file:
          - "./.env"

    nginx:
        image: codereviewvideos/nginx-wordpress
        hostname: nginx
        ports:
          - "801:80"
        volumes:
          - "./volumes/nginx/logs:/var/log/nginx/"
          - "./wordpress:/var/www/docker-wordpress-example.dev"
          - "./wp-config.php:/var/www/docker-wordpress-example.dev/wp-config.php"
        depends_on:
          - php
        env_file:
          - "./.env"

    php:
        image: codereviewvideos/php7-wordpress
        hostname: php
        volumes:
          - ./wordpress:/var/www/docker-wordpress-example.dev
          - ./wp-config.php:/var/www/docker-wordpress-example.dev/wp-config.php
        env_file:
          - "./.env"
        depends_on:
          - db

Ok, let's break this down, as this is really where the important stuff happens.

    db:
        image: mysql:5.7.20
        hostname: db
        volumes:
          - "./volumes/mysql:/var/lib/mysql"
        ports:
          - "33061:3306"
        env_file:
          - "./.env"

Our database service is going to use MySQL.

We're using the 5.7.20 release, which is the latest in the 5.x.x branch at the time of recording. As ever, check Docker Hub for the newest, shiniest release and update the tag accordingly.

hostname is so we can ping the container by the name db from other containers. This is important as our PHP container will need to be able to access db via this hostname, as per our configuration.

We don't need a volume in this instance, but we're using one anyway. Specifically, we are using a bind mount. The reasoning for this is that bind mounts are easier to work with on a Mac, which is what I use to record videos. On Linux a named volume is preferable, in my opinion.

Note that if we do not use a volume then our database data will be destroyed when we delete the database container.

We expose port 3306 as port 33061. You do not need to do this. However, this will allow you to connect using a desktop client - SequelPro, SQLYog, etc - by hitting 127.0.0.1, on port 33061, using the credentials from the .env file and work with your DB directly.

Finally the env_file makes sure that our db container has the expected environment variables set.

    nginx:
        image: codereviewvideos/nginx-wordpress
        hostname: nginx
        ports:
          - "801:80"
        volumes:
          - "./volumes/nginx/logs:/var/log/nginx/"
          - "./wordpress:/var/www/docker-wordpress-example.dev"
          - "./wp-config.php:/var/www/docker-wordpress-example.dev/wp-config.php"
        depends_on:
          - php
        env_file:
          - "./.env"

We're using the nginx image we created for our WordPress setup. This has all the configuration we need already pre-configured.

hostname in this case is not necessary, and could be omitted.

We expose port 80 on the container as port 801 locally. This means we can visit our nginx-powered site in our browser by visiting:

http://127.0.0.1:801/

You can change the port as needed.

Two of the volumes are very important here. The other, less so. Let's start with that one:

Firstly, "./volumes/nginx/logs:/var/log/nginx/" will create a new directory called 'volumes' on our machine in the current working directory.

Into this directory we want a subdirectory of nginx, and then another of logs. Docker will take care of this for us.

This directory is then mapped into the running container as /var/log/nginx.

In other words, any logs created by nginx will be easily accessible on our local system, even if the container is destroyed. This is very useful during development for troubleshooting.

Remember in our nginx config for our WordPress site we added two lines:

    error_log /var/log/nginx/docker-wordpress-example.dev_error.log;
    access_log /var/log/nginx/docker-wordpress-example.dev_access.log;

This is helpful to us.

The other two volume entries are the really important parts.

Firstly:

"./wordpress:/var/www/docker-wordpress-example.dev"

This maps our local wordpress subdirectory to be /var/www/docker-wordpress-example.dev inside the running container.

Remember our base PHP image does not contain any WordPress files. We must pass these in at runtime.

nginx needs access to these files also, even though it will really delegate / pass upstream for any PHP related work.

Again, this has been covered already in previous videos.

Secondly:

"./wp-config.php:/var/www/docker-wordpress-example.dev/wp-config.php"

Normally with WordPress you would copy the wp-config-sample.php to wp-config.php directly inside the WordPress directory.

We don't do that here.

Instead, we use Docker to map our local config as though it were really a file at /var/www/docker-wordpress-example.dev/wp-config.php.

In doing this I find it makes chances of accidentally blowing away the wp-config.php much harder. Also, it is easier to keep this file in version control, without needing to add all of the WordPress cruft into git.

It's your call of course, but this way works well for me.

Again though, nginx doesn't really do anything with this file.

We use depends_on to make sure nginx can't come up until php is up. No show without Punch.

And again env_file makes sure the environment variables are available in the running container as expected.

    php:
        image: codereviewvideos/php7-wordpress
        hostname: php
        volumes:
          - ./wordpress:/var/www/docker-wordpress-example.dev
          - ./wp-config.php:/var/www/docker-wordpress-example.dev/wp-config.php
        env_file:
          - "./.env"
        depends_on:
          - db

We've pretty much covered everything already.

Our custom WordPress image will be used. We made this in the previous video. There's not a whole lot to this, predominantly it makes sure we are in the right working directory.

hostname again is largely superfluous and could be omitted.

We've covered volumes in the nginx breakdown. This is exactly the same principle as before. However, our php container will do the processing in this example.

Again, env_file is as before, and depends_on means our PHP container won't be available until MySQL is up and running.

Docker Compose WordPress nginx - DONE

At this point we can spin up our Docker Compose WordPress nginx environment:

docker-compose up -d

And once our services are up and running:

http://127.0.0.1:81/

Immediately you should be redirected to the WordPress installation screen.

Where you choose to go from here is your call.

I use this very same approach in production (without the environment variables). The nice part about keeping the wp-config.php separate, only passing this in via a Docker volume command is that I can have a wp-config-production.php file completely isolated from the development config.

There are likely improvements and changes you could make here. This is a generic starting point that I've found works well for me. I hope you find it useful.

Code For This Course

Get the code for this course.

Episodes