Adventures with Docker #2: Working with containers.
In previous part this mini-series I described tool and main idea behind Docker. In this part, it's time to gets your hands dirty. We will prepare environment for our theoretical app based on CMS Drupal.
I'll show, how to prepare small database server and how our host (our workstation) can cooperate with containers running under control of Docker.
Creation of containers
On beginning, we will show, how to execute basic application in a container. We are taking old, good Drupal. What to do in order to install this CMS on our computer machine, in isolated, dedicated environment provided by Docker?
First, we have to find image, that includes Drupal. OK, but what exactly are those "images". You can think about this as a package with all libraries of base OS. This can be for example CentOS, Ubuntu or other Debian, with installed WWW server. For Docker project there is a quite big repository with images. Users can find something for them. You can also publish your own images, but this topic is out of scope of this post and will be described later.
From this huge library you have to select what interests you. We can search on tho ways: by using WWW interface or by using CLI interface. This second way is done by command docker search, which allows to quick find something interesting.
$ docker search drupal NAME DESCRIPTION STARS OFFICIAL AUTOMATED drupal Drupal is an open source 37 [OK] centurylink/drupal Drupal do 32 [OK] [OK]
Let's focus on column Official. This column says, if image is maintained by trusted team and it's official distribution. We can use other, unofficial images, but their quality can be .... different :)
Let's visit project page. As you can see, image exists in few variants (tags). Tags mechanism is used, in order to provide image in few "spices". For example, let's try to download Drupal in latest, unstable 8.x branch:
$ docker pull drupal:8 0c2770de12b2: Already exists 53a7ad4bdbbd: Already exists a1cf2f9b3404: Already exists e59f37741eaf: Already exists d22e71516cef: Already exists 9e4df0ca609b: Download complete 180e25acc056: Download complete 18cb893c6be4: Download complete 88fa3a887be9: Download complete c163c71a0264: Download complete f4b4197daf7a: Already exists f598334d420b: Already exists
After execution of this command, tool will download container image. If we would skip tag name (:8), docker will use default tag name (latest). Let's check of images list:
$ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE drupal 8 6a3953fc175d 11 days ago 565.2 MB
It's good to remember, that because Docker is using AUFS file system, images are built from layers, that are often shared between different images. For example, if you want to download latest version of image, only layers, that are missing on your local file system will be downloaded, not whole image.
OK, we have downloaded image, but how to execute this all? For this docker run is used. This command uses selected image, adds additional, writable layer to container (image layers are read only) and executes new command in context of such newly created container. To make things easier, maintainers of images very often set default command (ie. WWW server start), so you don't have to worry about this. Assuming, that we have image “centos” on our local machine, we can execute bash in that container:
$ docker run -t -i centos /bin/bash [root@7fddc7c2da5a /]#
OK, let's stop for a moment by this command. Parameter -t says, that console has to be attached to docker (because bash requires it). Parameter -i says, that docker should be executed in interactive mode. centos is image name, and /bin/bash to command name.
OK, let's return to our Drupal, how to set up and execute it on our machine using Docker?
$ docker run --name running_drupal -d -p 8080:80 drupal:8 02179c49e5e61de0ca5ea0868762eef1c27e6bc9f94773ffd31ce60081f0facc
We used few different options here: --name is a user friendly name of container, we can use it later in order to reference this container in other operations. -d is a request for calling container in a background mode. Very important option is -p 8080:80. It forwards port 80 from container as port 8080 on our host machine. After execution of this command, it will output sequence of letters and digits. This is unique id of freshly created container. We can use it (or friendly name provided by –name parameter) in order to referrer our container.
Container is working, now you can visit http://localhost:8080 with your browser and check if everything is OK. If it's OK, you should see Drupal installation page.
Working with existing containers
In previous step we executed container, running Drupal instance. It's working in background, how to stop or remove it?
You can use docker ps for that task. It will display all currently running containers.
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02179c... drupal:8 "apache2-foreground" 3 days ago Up 17 minutes 0.0.0.0:8080->80/tcp running_drupal
Meaning of columns, counting from left: unique identifier of container, image name, command name, date of creation, status, shared ports and friendly name of container.
Let's use this name in order to stop this container. We will use docker stop command for that.
$ docker stop running_drupal
After execution of this command, container is stopped. It doesn't use resources of our host machine. If you try to visit http://localhost:8080 with your browser again, you will fail. OK, let's start it again with docker start command:
$ docker start running_drupal
Let's try again with http://localhost:8080. Now we should once again see Drupal installation page.
How to display containers status
We can check, what processes are running in our container with command docker top:
$ docker top running_drupal UID PID PPID C STIME TTY TIME CMD root 10211 2153 0 10:51 ? 00:00:00 apache2 -DFOREGROUND www-data 10220 10211 0 10:51 ? 00:00:00 apache2 -DFOREGROUND www-data 10221 10211 0 10:51 ? 00:00:00 apache2 -DFOREGROUND
As you can see, it's only few apache2 processes. This illustrates one of biggest adventages of Docker: overhead coming from execution of containers is minimal – it's only matter of execution of few server applications, instead of running full stack operating system (like in more traditional full visualization).
We can also quickly check, what ports are available in our containers with docker port
$ docker port running_drupal 80/tcp -> 0.0.0.0:8080
You can also quickly check logs of applications, running in container – just use docker logs for that.
$ docker logs running_drupal AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.1. Set the 'ServerName' directive globally to suppress this message AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.1. Set the 'ServerName' directive globally to suppress this message PHP Warning: Module 'PDO' already loaded in Unknown on line 0 [Tue Jul 14 08:43:21.632435 2015] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.10 (Debian) PHP/5.6.10 configured -- resuming normal operations [Tue Jul 14 08:43:21.632464 2015] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND' 172.17.42.1 - - [14/Jul/2015:08:45:45 +0000] "GET / HTTP/1.1" 302 611 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:39.0) Gecko/20100101 Firefox/39.0" 172.17.42.1 - - [14/Jul/2015:08:45:45 +0000] "GET /core/install.php HTTP/1.1" 200 3750 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:39.0) Gecko/20100101 Firefox/39.0" 172.17.42.1 - - [14/Jul/2015:08:45:46 +0000] "GET /core/assets/vendor/domready/ready.min.js?v=1.0.8 HTTP/1.1" 200 690 "http://localhost:8080/core/install.php" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:39.0) Gecko/20100101 Firefox/39.0"
How to save changes in containers
I mentioned before, that images are not modifiable – you can modify only containers, created when you use docker run command. It means, that when you create new container, it will not share changes made by other containers. What to do, to commit such changes (like installed package, changed state of database, etc?). First, we can check, what changes in file system includes our container, please use docker diff to do that.
$ docker diff running_drupal C /run C /run/apache2 A /run/apache2/apache2.pid C /run/lock C /run/lock/apache2 C /var C /var/www C /var/www/html C /var/www/html/sites C /var/www/html/sites/prod.test.ratioweb.pl A /var/www/html/sites/prod.test.ratioweb.pl/files A /var/www/html/sites/prod.test.ratioweb.pl/files/translations A /var/www/html/sites/prod.test.ratioweb.pl/files/translations/drupal-8.0.0-beta12.pl.po
If changes seems to be OK, we can create new image using changes from container. We are using docker commit for that.
$ docker commit running_drupal drupal:8-installed
In this way, we created new image, that can be reused in a future.
How to remove unused containers
If we finish work with container and don't need it anymore, its good idea to remove it.
First, we will try to remove unused drupal:8 image with docker rmi command
$ docker rmi drupal Error response from daemon: Conflict, cannot delete 6a3953fc175d because the running container 02179c49e5e6 is using it, stop it and use -f to force Error: failed to remove images: [drupal:8]
Docker didn't allow removing this image. It detected running containers, that are using this image. Let's remove this container first with docker rm command.
$ docker rm running_drupal
After that step, it's perfectly safe to remove base image.
Communication between containers
It's important to notice, that our example container with Drupal, is quite poor in terms of infrastructure. It doesn't include database or cache server. During installation you can only use embedded sqllite database.
What if we would like to extend this environment and add MariaDB database? We can use Docker mechanism called “linking”. First, let's download the latest version of MariaDB image:
$ docker pull mariadb:10 $ docker -e MYSQL_ROOT_PASSWORD=mysecretpassword -e MYSQL_DATABASE=drupal -d run --name server_db mariadb
OK, we introduced one additional option in this command. -e allows injection of environment variables into running containers. In this case, scripts inside image of MariaDB will use it for setting up database instance and root server.
Now it's time to set up connection between our Drupal image and MariaDB. Please note, that I'm creating new Drupal container. When we are linking containers, we need to recreate them.
$ docker -d run --name drupal_server --link server_db:db drupal
In this command --link option is a key. This command says – container with name “server_db” will be available in container “drupal_server” under hostname “db”. Please note, that docker updates /etc/hosts file in containers, it doesn't modify your workstation hosts file. It means, that you will not be able to use this “db” hostname on your machine.
How to share directories between workstation and docker containers
Sometimes, you have to share parts of file system between your workstation and and container. If you are developing your own project, you have to share codebase of it. Sometimes, you want to have access for some containers directories, like /var/log subdirectory. I had lot of problems in a past with performance of IO operations on vbox shared directories. There are workarounds for that (like NFS) but those also aren't perfect.
If you want to share subdirectory with container, simple use -v option.
$ docker -d run --name drupal_server -v /path/on/server:/path/on/container
This -v says: /path/on/server is available as /path/in/container in container.
- Docker images repository: https://registry.hub.docker.com/
- Reference Documentation of Docker's CLI: http://docs.docker.com/reference/commandline/cli/