Install WordPress with Docker on Ubuntu 16.04

Table of Contents

Introduction

In this tutorial we will demonstrate how to install and configure WordPress (php:7.0-apache), Percona, phpMyAdmin and Redis using Docker containers on a Ubuntu Server running at ProfitBricks. We will use official repositories for the containers.

Requirements

  • Familarity using the ProfitBricks Data Center Designer (DCD).
  • A provisioned server running Ubuntu 16.04 LTS.
  • Shell acces to the server using SSH or the DCD's remote console.

Install Docker

We will install Docker from the official Docker repository. This ensures we will get the newest released version available:

sudo apt-get -y install docker.io

We can check the installed Docker version with docker version:

sudo docker version

The output will be similar to this:

Client:
 Version:      1.12.1
 API version:  1.24
 Go version:   go1.6.2
 Git commit:   23cf638
 Built:        Tue, 27 Sep 2016 12:25:38 +1300
 OS/Arch:      linux/amd64

Server:
 Version:      1.12.1
 API version:  1.24
 Go version:   go1.6.2
 Git commit:   23cf638
 Built:        Tue, 27 Sep 2016 12:25:38 +1300
 OS/Arch:      linux/amd64

Docker now be installed, the daemon started, and the process enabled to start on boot. And we can check the status of the Docker service:

sudo service docker status

The output will be similar to this:

* docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: e
   Active: active (running) since Tue 2016-12-13 13:09:38 UTC; 39s ago
     Docs: https://docs.docker.com
 Main PID: 21897 (dockerd)

Install Private Network

When we install Docker, it creates three networks automatically. These three networks are part of Docker's implementation. In many respects, a Docker network is really a namespace for containers that allows them to be grouped and segregated, plus communication channels. The docker network ls command will list the current networks and their IDs:

docker network ls

The output will be similar to this:

NETWORK ID          NAME                DRIVER              SCOPE
fdeb4241c25b        bridge              bridge              local
55b1bdccffbf        host                host                local
c520c4242141        none                null                local

When containers are launched, they can be assigned to a given network and will only be able to directly connect to other containers on the same network. Containers can publish services that allow them to be contacted via name. We can add containers to more than one network. Containers can only communicate within networks but not across networks. A container attached to two networks can communicate with member containers in either network.

Let's add a new bridge network. A new network can be created with the following command:

docker network create my_network

This has created a new network bridge called my_network name.

Note:

We can check whether the network has been created successfully with:

docker network ls

The output will be similar to this:

NETWORK ID          NAME                DRIVER              SCOPE
fdeb4241c25b        bridge              bridge              local
55b1bdccffbf        host                host                local
d9a9a51f3f7b        my_network          bridge              local
c520c4242141        none                null                local

We can get more details on the network from the network inspect command:

docker network inspect my_network

The output will be similar to this:

[
    {
        "Name": "my_network",
        "Id": "d9a9a51f3f7b5ca01a376bd7d7831a27b7983b39e91e9932a59f3a5822f95daf",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

Install Containers

The docker run command is used to run a process in a container. It is the go-to command for launching new containers. Its options allow users to configure how the Docker image is run, override Dockerfile settings, configure networking, and set privileges and resources for the container.

The --name option sets the name of the container. The name can then be used to address the container in other Docker commands.

The docker run command may be run in detached mode or attached mode. In detached mode the container is detached from the command line and the I/O is done through networking and shared volumes. The following command syntax would run a Docker container in a detached mode as indicated by the -d option. The option will run the container in the background and return the container ID.

The -p option is used to specify a port for the process running in the container. We can publish ports with the -p option. This option forwards ports on the host to the container. Alternatively, the -P option can be used to tell Docker to automatically select a free port to forward to on the host.

We can create a Redis container on the my_network network:

docker run --name redis -d -p 6379:6379 --net my_network redis

The output will be similar to this:

Unable to find image 'redis:latest' locally
latest: Pulling from library/redis
386a066cd84a: Pull complete
769149e3a45c: Pull complete
1f43b3c0854a: Pull complete
70e928127ad8: Pull complete
0764b5f23425: Pull complete
89d40b85d2c5: Pull complete
7ea61cb6ec2a: Pull complete
Digest: sha256:54057dd7e125ca41afe526a877e8bd35ec2cdd33b9217e022ed37bdcf7d09673
Status: Downloaded newer image for redis:latest
c83ccc45510b47076d7399299104ec0a70ece6c54c59703e0c9bbc9288f2f642

We created a Docker container for the Redis image in detached mode using the -d parameter, with container name as redis and port on which the application runs as 6379 using the -p parameter. Redis container joined my_network network with --net option. And redis took the value latest for not specifying tag.

When we ran the Redis application using the docker run command, the Docker client contacted the Docker daemon which searched for the Redis image in the local cache. Since the Redis image did not exist in the local cache, the Docker deamon downloaded the redis:latest image from the Docker Hub (https://hub.docker.com/r/library/redis/). Lastly, the Docker daemon created a Redis container from that image which runs the executable that produces the output we are currently reading. The Docker container for the Redis application was started.

Now we will start a new MySQL container. Other than the MYSQL_ROOT_PASSWORD environment variable, all the other variables are optional, we will run a MySQL instance container using this environment variable. The environment variables are specified with -e. Run the docker run command using the following command parameters:

docker run --name percona -d -e MYSQL_ROOT_PASSWORD="Set_A_Password" --net my_network percona

The output will be similar to this:

Unable to find image 'percona:latest' locally
latest: Pulling from library/percona
386a066cd84a: Already exists
827c8d62b332: Pull complete
de135f87677c: Pull complete
05822f26ca6e: Pull complete
ad65f56a251e: Pull complete
95f7455c9d4d: Pull complete
d306a7267d9b: Pull complete
f32591bd152c: Pull complete
c8d0a4a6c2de: Pull complete
e6f6cae8b1ad: Pull complete
cd6a0883c7d8: Pull complete
Digest: sha256:dcf9b3b2de78d28d88fcc91e48cdfe7890e40d9db45c69d2db12b6039265603b
Status: Downloaded newer image for percona:latest
66161ae23bcbc27e29e76655191cc578028ed1e25e08736d5800d4ffa728990a

It is possible to log into a MySQL CLI shell. But first we need to start the percona interactive terminal or shell. To execute a command inside a container, use the docker exec command:

sudo docker exec -it percona bash

In the interactive terminal run the following command:

mysql -u root -p

The MySQL CLI gets started after providing the MySQL root password that we specified when launching the container. ("Set_A_Password") Now we can query and interact with the relational database as needed.

The default images do not install or run an SSH daemon by default. The normal way of getting a shell is to use the docker exec command, which avoids the penalty of running an unnecessary process per container.

We can create and publish a phpMyAdmin container on port 8080. Set PMA_HOST=percona to define the name of our MySQL container/server:

docker run --name phpmyadmin -d -p 8080:80 --net my_network -e PMA_HOST=percona phpmyadmin/phpmyadmin

The output will be similar to this:

Unable to find image 'phpmyadmin/phpmyadmin:latest' locally
latest: Pulling from phpmyadmin/phpmyadmin
3690ec4760f9: Pull complete
0a91285314fc: Pull complete
f88affdab991: Pull complete
bb77e9eafa1c: Pull complete
dc01a2c467f9: Pull complete
17e1d40dc961: Pull complete
Digest: sha256:154d8d385f042535652c33617f91516424f97a0f2fdc824290059db9f9d25987
Status: Downloaded newer image for phpmyadmin/phpmyadmin:latest
c239eac0f91185078c3ae681c01f595cab071825c7ba6958b0fa54fd01c3cf48

Now we can create a container for WordPress with PHP 7 and Apache. We will use php7.0 tag. As we know Wordpress need percona container so we provide a pointer to our container with WORDPRESS_DB_HOST environment:

docker run --name wordpress -d -p 80:80 --net my_network -e WORDPRESS_DB_HOST=percona -e WORDPRESS_DB_PASSWORD="Set_A_Password" wordpress:php7.0

The output will be similar to this:

Unable to find image 'wordpress:php7.0' locally
php7.0: Pulling from library/wordpress
386a066cd84a: Already exists
269e95c6053a: Pull complete
6243d5c57a34: Pull complete
872f6d38a33b: Pull complete
e5ea5361568c: Pull complete
f81f18e77719: Pull complete
f9dbc878ca0c: Pull complete
195935e4100b: Pull complete
af3b8f99329b: Pull complete
d164dd2f74e9: Pull complete
35125ac3ca7e: Pull complete
734d7da6250e: Pull complete
6a48cde0e7c2: Pull complete
8637835e0297: Pull complete
6807581dccb7: Pull complete
4914cf691556: Pull complete
1b32cfb90894: Pull complete
ed4b9b9939ae: Pull complete
33cf948c4285: Pull complete
Digest: sha256:47ce44aa68a7b539108d48a2f9087bd12e91074b77285ed3dbfc8c3bac636d13
Status: Downloaded newer image for wordpress:php7.0
baee6de626ce5558496ea0482f61cf517ba2bf64e7cb6e5099035a10ae67185f

When we need to edit Apache config files, first we need to start the wordpress shell:

sudo docker exec -it wordpress bash

And default Apache configuration files located at /etc/apache2/ directory.

We finished container setups. We can lists all running containers with docker ps:

docker ps

The output will be similar to this:

IMAGE                   COMMAND                  STATUS              PORTS                    NAMES
wordpress:php7.0        "docker-entrypoint.sh"   Up 42 seconds       0.0.0.0:80->80/tcp       wordpress
phpmyadmin/phpmyadmin   "/run.sh phpmyadmin"     Up About a minute   0.0.0.0:8080->80/tcp     phpmyadmin
percona                 "docker-entrypoint.sh"   Up 4 minutes        3306/tcp                 percona
redis                   "docker-entrypoint.sh"   Up 5 minutes        0.0.0.0:6379->6379/tcp   redis

Please Note:

If your container is no longer running or has stopped, you need to use the -a parameter to list all available containers. You can get a list of all containers including, stopped containers, with docker ps -a.

To delete a container, you can use the docker rm Container-Name command. It removes the container only. Downloaded local images will still be available, and can be used to start a new container. We can remove local images with docker rmi Container-Name command. Local images can be listed by running docker images command.

docker images -a
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
wordpress               php7.0              71c19c8a553d        3 days ago          419.1 MB
percona                 latest              62909c52ff9d        6 days ago          392.9 MB
redis                   latest              d4f259423416        13 days ago         105.9 MB
phpmyadmin/phpmyadmin   latest              200931982ab6        2 weeks ago         110.4 MB

We can get details of the my_network private network with the docker network inspect command again. It should list containers in the my_network network:

docker network inspect my_network

The output will be similar to this:

[
    {
        "Name": "my_network",
        "Id": "d9a9a51f3f7b5ca01a376bd7d7831a27b7983b39e91e9932a59f3a5822f95daf",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "66161ae23bcbc27e29e76655191cc578028ed1e25e08736d5800d4ffa728990a": {
                "Name": "percona",
                "EndpointID": "ccb7bfa26cc966b87f74607c360b88662b7c46af8606254aa2ce9fe28594331c",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "baee6de626ce5558496ea0482f61cf517ba2bf64e7cb6e5099035a10ae67185f": {
                "Name": "wordpress",
                "EndpointID": "fd1fbb4070a04762145eb7343e1dd9066109c25001890405d303f2c67ff7be43",
                "MacAddress": "02:42:ac:12:00:05",
                "IPv4Address": "172.18.0.5/16",
                "IPv6Address": ""
            },
            "c239eac0f91185078c3ae681c01f595cab071825c7ba6958b0fa54fd01c3cf48": {
                "Name": "phpmyadmin",
                "EndpointID": "5030b9ebfbf12a68f5a1a2c6baece2dfc17eb0aa0d5a43e85f6773feb16d191a",
                "MacAddress": "02:42:ac:12:00:04",
                "IPv4Address": "172.18.0.4/16",
                "IPv6Address": ""
            },
            "c83ccc45510b47076d7399299104ec0a70ece6c54c59703e0c9bbc9288f2f642": {
                "Name": "redis",
                "EndpointID": "3cb4c08e9fdbc98802bed35747ebadade798cf2e95d06eeaacb4965e0da02659",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

We can monitor the status of our containers by utilizing the docker stats command. That returns a live stream of resource usage. The command takes the name of one or more containers and prints various statistics for them, in much the same way as the Unix application top:

docker stats percona

The stats cover CPU and memory usage as well as network utilization. Note that unless you have set memory limits on the container, the limit you see on memory will represent the total amount of memory on the host, rather than the amount of memory available to the container.

Most of the time, we will want to get stats for all the running containers on the host:

docker stats $(docker ps -q)

If you want to display stats with the container names:

docker stats $(docker ps --format={{.Names}})

Configure Redis Cache

We can access the Wordpress installation at our domain or IP address. To use our Redis container for object cache storage, we need to download and install the Redis Object Cache plugin. Before we do that, we need to add the some lines to the wp-config.php file.

We need to learn the location of the Wordpress files/directories that were created inside the container. Wordpress files/directories in the container volume. We can locate the volume of Wordpress container by utilizing the docker inspect command. The output will provide details on the Wordpress container configuration including the volumes:

docker inspect wordpress

The output will be similar to this:

.
.
.
},
 "Mounts": [
     {
         "Name": "8772ddbf5bc6882644d3e497bed97f48431a40811c0d697b765282de215cfe1a",
         "Source": "/var/lib/docker/volumes/8772ddbf5bc6882644d3e497bed97f48431a40811c0d697b765282de215cfe1a/_data",
         "Destination": "/var/www/html",
         "Driver": "local",
         "Mode": "",
         "RW": true,
         "Propagation": ""
     }
],
.
.
.

The container files are located at /var/lib/docker/volumes/Your-Container-ID/_data/ location.

Open the wp-config.php file:

sudo vi /var/lib/docker/volumes/Your-Container-ID/_data/wp-config.php

Add the following lines below the DB_HOST and DB_NAME entries:

define('WP_REDIS_HOST', 'redis');

define('WP_CACHE_KEY_SALT', 'wordpress');

Save changes and close the /var/lib/docker/volumes/Your-Container-ID/_data/wp-config.php file.

Go to your Wordpress admin dashboard and install and activate the Redis Object Cache plugin.

Note:

  • Settings > Redis and click the button to activate.

Once all installation and configuration has successfully completed, we can access:

  • Wordpress: http://Your-IP
  • phpMyAdmin: http://Your-IP:8080

Summary

We have successfully installed WordPress (php:7.0-apache), Percona (MySQL), phpMyAdmin, and Redis using Docker containers on a Ubuntu 16.04 server. I hope you were able to follow through the tutorial successfully.

You are welcome to leave feedback in the comments section below. If you happen to run into any issues, we may be able to provide some direction or clarification to assist in getting things resolved.

References