This is part 3 of my series of Docker articles, which will run you through the basics of Docker and provide an easy to follow learning path. If you haven’t gone through the first two articles (What is Docker? and Docker Basics: A practical starters guide), these will give you an overview of Docker as well as a guide to getting started.
So following on from the previous article, you should now be able to deploy a container. One of the key features of Docker is the ability to run different services in their own container. For example, a typical WordPress site would look like this:
Generally, these services are all run on the one virtual server (or even shared hosting environment) where you may or may not have configuration management control. In a Docker environment, we want to separate these into their own containers.
So what’s the advantage of breaking each service out into individual containers? There's two key areas which stand out immediately.
First and foremost is version control. If you have all of the services on the one server, you end up with a complex dependency chain for system level libraries which can make upgrades and migrations fun. It also means you can upgrade and test different versions of particular services without affecting anything at a system layer. For example, if you’re running WordPress and another PHP driven system from the same server, you can independently maintain the PHP versions per service.
Secondly, you get the benefit of isolation. Each container can be resource limited via controls over system level aspects such as memory and CPU. This gives you fine-grained control and will also ensure an even distribution of resources between services on your server. It also limits the potential data leak between services if one happens to be compromised.
Taking our WordPress scenario, lets look at how we configure this. Most of the details are the same as deploying a single container but with a few small tweaks. Lets start by deploying a MySQL server and because we can easily tweak the version, lets go for version 5.7:
docker run --name db-server -d -e MYSQL_ROOT_PASSWORD=Memzoh78 -e MYSQL_DATABASE=wordpressdemo -e MYSQL_USER=conetixdemo -e MYSQL_PASSWORD=Xumkos26 -v /test/mysqldata:/var/lib/mysql mysql:5.6
If you’ve only run the basic tests from my previous article, you’ll see there’s a lot more complexity to this.
Firstly, lets go through the new parts:
-e MYSQL_ROOT_PASSWORD=Memzoh78 -e MYSQL_DATABASE=wordpressdemo -e MYSQL_USER=conetixdemo -e MYSQL_PASSWORD=Xumkos26
The -e sets the environment variables, which can be used to pass setup and configuration parameters to your container. For MySQL, these particular variables set the root password as well as create a database with the username and password specified. As you’ll see further down in the article, these can then be used by other applications to talk to the database.
The second part which is new is the -v, which sets the volume mapping. Rather than the data remaining within the Docker container, we map the volume to a specific directory within the host server. This way, we can destroy and re-create the container for the MySQL server without any data loss. For those who are really astute and see this as a “bad” fit for the Docker portability, you are correct. The answer to this is Docker Data Containers, which is another step we need to do first. To do this, lets create a data container for our database:
docker create -v /var/lib/mysql --name db-data mysql:5.6 /bin/true
It’s important to note that while we use the MySQL image for the command to create the data container, it’s simply because we want to re-use the same image and save having to download any further images. While this is a bit confusing to begin with, we now have a data container which we can reference by name from our MySQL server container. Because all of the data is held in a separate container, we can delete and modify the MySQL server independently of the data. We now have portable containers for the data as well as the services too.
Lets quickly remove the old one and use the new data container reference:
docker stop db-server docker rm db-server
docker run --name db-server -d -e MYSQL_ROOT_PASSWORD=Memzoh78 -e MYSQL_DATABASE=wordpressdemo -e MYSQL_USER=conetixdemo -e MYSQL_PASSWORD=Xumkos26 --volumes-from db-data mysql:5.6
We now have our database configured and complete.
We’ll start off by running the official WordPress image provided by Docker:
docker run --name wordpress-demo --link db-server:mysql -d -e WORDPRESS_DB_NAME=wordpressdemo -e WORDPRESS_DB_USER=conetixdemo -e WORDPRESS_DB_PASSWORD=Xumkos26 -p 8080:80 wordpress
This will download and run the latest version of WordPress, using the latest version available, set the database parameters for WordPress and link it to the database server automatically. The –link command allows you to automatically establish a connection between the two containers so that they can share information as well as network connections. It’s a very simple command but it greatly reduces the complexity of your configuration.
This is a very basic WordPress configuration, so it includes Apache within the container as well. Of course, for more complex setups and for further control this can be split into separate containers as well. For now, we’ll use the container with the combined services.
The -p command mapped the port from the container (port 80) to the host (port 8080) so that we can access it. You should be now able to load http://[boot2dockerip]:8080 and see the setup screen for the demo WordPress site.
If you want to have some real fun, try stopping, deleting and re-creating the WordPress container. It should be less than 5 seconds in total for all of the commands, this is the speed of Docker!
Lets now add a Docker data container for our WordPress installation. Again, lets remove the existing container first:
docker stop wordpress-demo docker rm wordpress-demo
Now, lets create the data container:
docker create -v /var/www/html --name wp-data wordpress /bin/true
Then, re-create the WordPress container with the new data container mapping:
docker run --name wordpress-demo --link db-server:mysql -d -e WORDPRESS_DB_NAME=wordpressdemo -e WORDPRESS_DB_USER=conetixdodemo -e WORDPRESS_DB_PASSWORD=Xumkos26 -p 8080:80 --volumes-from wp-data wordpress
Unleashing the power of Docker
Like the MySQL example, because our data is separate from the WordPress container, we can manipulate the files using another container quite easily. For example, lets run a backup:
docker run --volumes-from wp-data -v $(pwd):/backup ubuntu tar czf /backup/wordpress-backup.tar.gz /var/www/html
In this example, we used the data container with the WordPress data, mapped our current directory via $(pwd) to /backup, fired up the latest version of Ubuntu and then ran the tar command to backup /var/www/html. If you already had a copy of the latest Ubuntu pulled down, running the command itself should have only taken a few seconds.
Now, let’s test an upgrade of MySQL.
First, we need to stop and remove the container running the database:
docker stop db-server docker rm db-server
Remember, our data isn’t within this container, so you’re only removing the service. Now lets create our new MySQL server running 5.7:
docker run --name db-server -d -e MYSQL_ROOT_PASSWORD=Memzoh78 -e MYSQL_DATABASE=wordpressdemo -e MYSQL_USER=conetixdemo -e MYSQL_PASSWORD=Xumkos26 --volumes-from db-data mysql:5.7
We can safely pass the same MYSQL_DATABASE environment variables, as the Docker MySQL script won’t actually delete the database if it already exists.
Because we upgraded database versions, there’s a few other quick steps invovled. The first is to run the ‘mysql_upgrade’ command, so that we ensure the schemas are all brought up to the correct version. We can call an executable within the container to do this:
docker exec db-server mysql_upgrade -p'Memzoh78'
Next, because we’ve re-created the database server we need to re-create the WordPress container so that the link picks up the new container. If you browse to the site at the moment, you’d see an error like this:
This is because although the MySQL containers are named the same, they’re treated as completely different entities and the existing link is no longer valid. So, lets quickly rebuild it:
docker stop wordpress-demo docker rm wordpress-demo docker run --name wordpress-demo --link db-server:mysql -d -e WORDPRESS_DB_NAME=wordpressdemo -e WORDPRESS_DB_USER=conetixdemo -e WORDPRESS_DB_PASSWORD=Xumkos26 -p 8080:80 --volumes-from wp-data wordpress
Apart from the database upgrade script, all of this occurs within seconds.
Hopefully now you understand the process of linking the containers together as well as how volumes and data containers work. Here's what our end solution looks like:
We've broken the single server out into separate services, as well as ensured that the services remain immutable so that they can be destroyed and re-created at any time. There’s still a fair amount of work to configure a basic WordPress instance with the separate database and data containers, but the good news is there’s already a tool to help with this (which we’ll cover in a few weeks). Once again, we'll cover the process to turn your WordPress instance into a single command deploy again with an easy to follow configuration file.
If you have any questions, please feel free to ask in the comments below!