Adventures with Docker #2: Working with containers.

jsobiecki's picture
Adventures with Docker #2: Working with containers.

Introduction

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?

Displaying containers

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.

References

  • Docker images repository: https://registry.hub.docker.com/
  • Reference Documentation of Docker's CLI: http://docs.docker.com/reference/commandline/cli/