I wrote quick "Hands-on" for beginners to cover docker basics with simple containers so I won't go into details of complex application, service definitions or swarms. What I want to do is run a webserver inside a container with persistent data

Check Docker

Let's check if docker is running

[email protected]:~$ docker version
Client:
 Version:      1.12.6
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   78d1802
 Built:        Tue Jan 31 23:47:34 2017
 OS/Arch:      linux/amd64
Server:
 Version:      1.12.6
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   78d1802
 Built:        Tue Jan 31 23:47:34 2017
 OS/Arch:      linux/amd64

For now there are no images available but this will change in a little:

[email protected]:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZ

There are also no running containers:

[email protected]:~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                          PORTS               NAMES

Maybe also quickly check the network configuration especially what IP's our docker host has:

[email protected]:~$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 02:75:fc:25:d7:25 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet6 fe80::75:fcff:fe25:d725/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:08:be:4c brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.10/24 brd 192.168.33.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe08:be4c/64 scope link
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:d4:b5:a1:57 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever

Get your first image

Images have to be downloaded to the docker host once in order to be able to create containers. You can simply pull images with pull command{.external-link}

$ docker pull [OPTIONS] NAME[:TAG|@DIGEST]

Once done, you can see the images with images command{.external-link}. So let's pull the webserver image ngnix and then check

[email protected]:~$ docker pull nginx
....
57168433389f: Extracting [===============================================>   ] 20.64 MB/21.53 MB
57168433389f: Extracting [================================================>  ] 20.87 MB/21.53 MB
57168433389f: Extracting [=================================================> ]  21.1 MB/21.53 MB
57168433389f: Extracting [=================================================> ] 21.33 MB/21.53 MB
57168433389f: Extracting [==================================================>] 21.53 MB/21.53 MB
57168433389f: Pull complete

332ec8285c50: Extracting [==================================================>]    193 B/193 B
332ec8285c50: Extracting [==================================================>]    193 B/193 B
332ec8285c50: Pull complete
Digest: sha256:c15f1fb8fd55c60c72f940a76da76a5fccce2fefa0dd9b17967b9e40b0355316
Status: Downloaded newer image for nginx:latest
[email protected]:~$ images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              46102226f2fd        12 hours ago        109.

Details on how this image is composed can be found by checking the image description on Docker-Hub, in the original Dockerfile or by inspecting the image with the docker inspect{.external-link} nginx command

Start your first container

A container can be started by docker run{.external-link} command:

[email protected]:~$ docker run nginx

As you can see, so far nothing else happens. Well not entirely true, let's quickly open another terminal and check whether we have a running container

[email protected]:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
a4a7e4f36d5b        nginx               "nginx -g 'daemon off"   3 minutes ago       Up 3 minutes        80/tcp              pedantic_yonath

So there is a container running and it is named "pedantic_yonath" - this name is given automatically by docker, but you can force a proper name by aggregating parameter -name. So when the container is running, then we should be able to connect to the webserver right? Well first we need to figure out the ip address of the container, which can be simply achieved by inspect command

[email protected]:~$ docker inspect pedantic_yonath
[
    {
        "Id": "a4a7e4f36d5b5c19d6f67381726dbe54b5feec81aeec150b816a81d8a671fd0f",
        "Created": "2017-04-26T13:54:36.632963616Z",
        "Path": "nginx",
        "Args": [
            "-g",
            "daemon off;"
        ],
...
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02"
                }
            }
        }
    }
]
[email protected]:~$ docker inspect --format='' pedantic_yonath
172.17.0.2

The networking we will discuss later, importantly is to see that the docker container is accessible via 172.17.0.2 from the docker host, so we use curl to see if our webserver is working:

[email protected]:~$ curl 172.17.0.2
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Running your container 2nd part

When we switch back to terminal 1 we see some more information about our recently request done with curl:

[email protected]:~$ docker run nginx
172.17.0.1 - - [26/Apr/2017:14:09:31 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.50.1" "-"
<div>
  <div id="highlighter_944046" class="syntaxhighlighter nogutter java">
    <table border="0" cellspacing="0" cellpadding="0">
      <tr>
        <td class="code">
          <div class="container" title="Hint: double-click to select code">
            <div class="line number4 index3 alt1">
              <code class="java plain"> </code>
            </div>
          </div>
        </td>
      </tr>
    </table>
  </div>
</div>

We have confirmation that the nginx-container is actually working but now we cannot continue to work on terminal 1 i.e. start another container or else. Let's switch back to terminal 2 and let's start a new container

  1. Container shall have a proper name
  2. Container shall run in background and not in foreground
[email protected]:~$ docker run --name webserver -d nginx
2a5b06c6b3eb107833d765db286fc46278d622a9ab537d118a437ced2b501d4f
[email protected]st:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
2a5b06c6b3eb        nginx               "nginx -g 'daemon off"   21 seconds ago      Up 20 seconds       80/tcp              webserver
a4a7e4f36d5b        nginx               "nginx -g 'daemon off"   32 minutes ago      Up 32 minutes       80/tcp              pedantic_yonath

Now we have two container instances running. You might try the same exercise as before by running curl against container "webserver". Sill wondering what happened within the container? Use docker logs{.external-link} command:

[email protected]:~$ docker logs webserver
172.17.0.1 - - [26/Apr/2017:14:28:28 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.50.1" "-"

At the moment we can access the webserver only from within the docker host which is not very convenient. Therefore let's expose port 80 from the nginx to one port of the host. As no webserver is running on the docker host we can actually use port 80. The port mapping can happen on start of a container by parameter -p{.external-link} - as we already have a container with name "webserver" running I use "webserver2":

[email protected]:~$ docker run --name webserver2 -p 80:80 -d nginx
05db14bc6ecd0c375482ae9ef353e651501cc51cc86d8e53d7ca826ac3a1ec10
[email protected]:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
05db14bc6ecd        nginx               "nginx -g 'daemon off"   8 seconds ago       Up 7 seconds        0.0.0.0:80->80/tcp   webserver2
2a5b06c6b3eb        nginx               "nginx -g 'daemon off"   17 hours ago        Up 17 hours         80/tcp               webserver
a4a7e4f36d5b        nginx               "nginx -g 'daemon off"   17 hours ago        Up 17 hours         80/tcp               pedantic_yonath

Now I can reach the webserver2 also outside from the docker host. If you check above the IP of the docker host is  192.168.33.10 so from your working computer you can use a browser:

nginx welcome page

Change your data

For the next steps I need an self-prepared index.html file which contains a different content thant the original one, as I want to replace the default one from the nginx installation with this. I store my index.html on the docker host in /var/www and then use the docker cp{.external-link} command to copy the file do the container:

[email protected]:~$ docker cp /var/www/index.html webserver2:/usr/share/nginx/html/

nginx welcome page

Connect to container

Under some circumstances you would like to work on the container e.g. to temporarily modify some configurations. You can use as of docker attach{.external-link} or docker exec{.external-link}:

[email protected]:~$ docker attach webserver2

Unfortunately when attaching to the webserver2 container nothing else happens. We have experienced this already above when we launched our first container. This is due to the command exeucted by docker (as specified in the image) which is CMD ["nginx", "-g", "daemon off;"] - means ngnix is running in foreground and not a shell like bash. Now you might want to press CTRL-C but don't as this will kill the container. Use instead CTRL-p CTRL-q which is the key sequence to de-attach from a container. In such cases use the exec command:

[email protected]:~$ docker exec -i -t webserver2 bash
[email protected]:/# cat /usr/share/nginx/html/index.html
<html>
   <head>
   </head>
   <Body>
      <h1>Docker Tutorial</h1>
      <p>This is an example page for my docker tutorial</p>
   </Body>
</html>

Stopping Containers

Stopping containers is done by the stop command{.external-link}

[email protected]:~$ docker stop webserver2webserver2
[email protected]:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
2a5b06c6b3eb        nginx               "nginx -g 'daemon off"   18 hours ago        Up 4 seconds        80/tcp              webserver
a4a7e4f36d5b        nginx               "nginx -g 'daemon off"   19 hours ago        Up 19 hours         80/tcp              pedantic_yonath

The container webserver2 is stoped but not yet deleted. You can see this with docker ps -a command{.external-link}

[email protected]:~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                          PORTS               NAMES
05db14bc6ecd        nginx               "nginx -g 'daemon off"   About an hour ago   Exited (0) About a minute ago                       webserver2
2a5b06c6b3eb        nginx               "nginx -g 'daemon off"   18 hours ago        Up About a minute               80/tcp              webserver
a4a7e4f36d5b        nginx               "nginx -g 'daemon off"   19 hours ago        Up 19 hours                     80/tcp              pedantic_yonath

You can start the container again and your webserver is serving again your website:

[email protected]:~$ docker start webeserver2
webserver2
[email protected]:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
05db14bc6ecd        nginx               "nginx -g 'daemon off"   About an hour ago   Up 16 seconds       0.0.0.0:80->80/tcp   webserver2
2a5b06c6b3eb        nginx               "nginx -g 'daemon off"   18 hours ago        Up 4 minutes        80/tcp               webserver
a4a7e4f36d5b        nginx               "nginx -g 'daemon off"   19 hours ago        Up 19 hours         80/tcp               pedantic_yonath

Removing containers

If we want to remove containers we a) need to explicitly do this by docker rm command{.external-link} or b) once specified the -rm parameter with docker run command{.external-link}. As we did not use -rm option when starting our containers we have to do this explicitly:

[email protected]:~$ docker stop webserver2
webserver2
[email protected]:~$ docker stop webserver
webserver
[email protected]:~$ docker stop pedantic_yonath
pedantic_yonath
[email protected]:~$ docker rm webserver2
webserver2
[email protected]:~$ docker rm webserver
webserver
[email protected]:~$ docker rm pedantic_yonath
pedantic_yonath
[email protected]:~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[email protected]:~$

Working with Data

As soon as we deleted our webserver2 container we also lost the changes i.e. the data (index.html) from within that container. When we launch a new container it will just simply show the default nginx welcome page. Therefore data should be made persistent. There exists two ways{.external-link} Data Volumes and Data Containers.

Data Volumes

Using data volumes means mapping a host directory (or file) to a directory (or file) within the container. For example our index.html file is on the docker host in /var/www so just let's map that into the default webserver root from nginx:

[email protected]:~$ docker run -d -v /var/www:/usr/share/nginx/html
fef0cc126b5a087bea22191083e6260ee54a06faeb384b5c22af3c98f8bde413

[email protected]:~$ docker inspect webserver
...
        "Mounts": [
            {
                "Source": "/var/www",
                "Destination": "/usr/share/nginx/html",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
...

Check it out:

[email protected]:~$ curl 172.17.0.2
<html>
    <head>
    </head>
    <Body>
        <h1>Docker Tutorial</h1>
        <p>This is an example page for my docker tutorial</p>
    </Body>
</html>

Now you can actually do some modification of the file. For example let's add another paragraph and the check the file on the docker host:

[email protected]:~$ docker exec -it webserver bash
[email protected]:/# sed -i 's/<\/p>/<\/p><p>This is a new line added from within a container<\/p>/g' /usr/share/nginx/html/index.html
[email protected]:/# cat /usr/share/nginx/html/index.html
<html>
   <head>
   </head>
   <Body>
      <h1>Docker Tutorial</h1>
      <p>This is an example page for my docker tutorial</p><p>This is a new line added from within a container</p>
   </Body>
</html>

[email protected]:/# exit
[email protected]:~$ cat /var/www/index.html
<html>
   <head>
   </head>
   <Body>
      <h1>Docker Tutorial</h1>
      <p>This is an example page for my docker tutorial</p><p>This is a new line added from within a container</p>
   </Body>
</html>

The problem with data volumes is that the mapping the mapping is tied to the host, so if the docker host you are running your docker on does not have the data or a different folder structure the mapping does not work. Therefore you may use volume plugins{.external-link} to provision and mount shared storage, such as iSCSI, NFS, or FC.

Data Containers

The docker team actually recommends to use data containers although there are advantages and disadvantages for both of the variants (interesting reading: https://medium.com/@ramangupta/why-docker-data-containers-are-good-589b3c6c749e{.external-link})

If you have some persistent data that you want to share between containers, or want to use from non-persistent containers, it’s best to create a named Data Volume Container, and then to mount the data from it.

So let's create a data container for our webserver which will contain our index.html.

[email protected]:~$ docker create -v /usr/share/nginx/html --name webdata nginx
c524bcb0f1351746512e3fe92d0a8d4ee72a920b3a79ae4bcf246f3c8e46ed34
[email protected]:~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
c524bcb0f135        nginx               "nginx -g 'daemon off"   10 seconds ago      Created                                  webdata
fef0cc126b5a        nginx               "nginx -g 'daemon off"   About an hour ago   Up About an hour    0.0.0.0:80->80/tcp   webserver

We still need to put the data into the data container:

[email protected]:~$ docker cp /var/www/index.html webdata:/usr/share/nginx/html/

Now let's start a new container using the data volume

[email protected]:~$ docker run -d --volumes-from webdata --name web1 -p 81:80 nginx
acc3caeed42a35096bee3655937b7007c0ac679bc24c446d993315b790ce4630
[email protected]:~$ curl 172.17.0.3
<html>
    <head>
    </head>
    <Body>
        <h1>Docker Tutorial</h1>
        <p>This is an example page for my docker tutorial</p><p>This is a new line added from within a container</p>
    </Body>
</html>