Long running services with Docker

Overview

Teaching: 10 min
Exercises: 5 min
Questions
Objectives
  • Learn how to start containers for a running (web) service

Starting a long-running service in a container

Containers are useful for running services, like web-servers etc. Many come packaged from the developers, so you can start one easily, but first you need to find the one you want to run. You can either search on Docker Hub, or you can use the docker search command. Nginx is a popular web-server, let’s look for that:

$ docker search nginx
NAME                      DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
nginx                     Official build of Nginx.                        4719      [OK]       
jwilder/nginx-proxy       Automated Nginx reverse proxy for docker c...   877                  [OK]
richarvey/nginx-php-fpm   Container running Nginx + PHP-FPM capable ...   311                  [OK]
million12/nginx-php       Nginx + PHP-FPM 5.5, 5.6, 7.0 (NG), CentOS...   76                   [OK]
webdevops/php-nginx       Nginx with PHP-FPM                              63                   [OK]
maxexcloo/nginx-php       Framework container with nginx and PHP-FPM...   58                   [OK]
bitnami/nginx             Bitnami nginx Docker Image                      20                   [OK]
gists/nginx               Nginx on Alpine                                 8                    [OK]
evild/alpine-nginx        Minimalistic Docker image with Nginx            8                    [OK]
million12/nginx           Nginx: extensible, nicely tuned for better...   8                    [OK]
maxexcloo/nginx           Framework container with nginx installed.       7                    [OK]
webdevops/nginx           Nginx container                                 7                    [OK]
1science/nginx            Nginx Docker images based on Alpine Linux       4                    [OK]
ixbox/nginx               Nginx on Alpine Linux.                          3                    [OK]
drupaldocker/nginx        NGINX for Drupal                                3                    [OK]
yfix/nginx                Yfix own build of the nginx-extras package      2                    [OK]
frekele/nginx             docker run --rm --name nginx -p 80:80 -p 4...   2                    [OK]
servivum/nginx            Nginx Docker Image with Useful Tools            2                    [OK]
dock0/nginx               Arch container running nginx                    2                    [OK]
blacklabelops/nginx       Dockerized Nginx Reverse Proxy Server.          2                    [OK]
xataz/nginx               Light nginx image                               2                    [OK]
radial/nginx              Spoke container for Nginx, a high performa...   1                    [OK]
tozd/nginx                Dockerized nginx.                               1                    [OK]
c4tech/nginx              Several nginx images for web applications.      0                    [OK]
unblibraries/nginx        Baseline non-PHP nginx container                0                    [OK]

The official build of Nginx seems to be very popular, let’s go with that:

$ docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
386a066cd84a: Pull complete
386dc9762af9: Pull complete
d685e39ac8a4: Pull complete
Digest: sha256:3861a20a81e4ba699859fe0724dc6afb2ce82d21cd1ddc27fff6ec76e4c2824e
Status: Downloaded newer image for nginx:latest

Now, in order to run a web server such as a Nginx, we are going to use some additional Docker options, to:

We will also use a number of new Docker commands. Let’s start with a known one and add some extra flags:

$ docker run -p 80:80 --name=nginx nginx

The option -p 80:80 tells Docker to map port 80 on the host to port 80 in the container, so you can communicate with it.

We’ve also introduced a second new option: --name. Docker automatically names containers, but these names don’t reflect the purpose of the container (e.g. pensive_booth was the name of the nginx container I ran for this example without the --name option). You can name your container whatever you want, but it’s helpful to give it a name similar to the container you’re using, or the specific service or application you’re running in the container. This can be useful when we need to act on the container while it’s running, e.g. to stop it, or to get logs, as we’ll see soon. In this example, we’ve called our container nginx.

Note also that we didn’t tell docker what program to run, that’s baked into the container in this case. More on that later.

Now, go to your browser and in the address bar enter localhost if you are running Docker on your machine, or <Your VM's IP Address> if you are running on a cloud service. You should see a page with a Welcome to nginx! message. On your terminal where you ran the docker command, you’ll see some log information:

172.17.0.1 - - [30/Nov/2016:18:07:59 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:49.0) Gecko/20100101 Firefox/49.0" "-"
2016/11/30 18:07:59 [error] 7#7: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost"
172.17.0.1 - - [30/Nov/2016:18:07:59 +0000] "GET /favicon.ico HTTP/1.1" 404 169 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:49.0) Gecko/20100101 Firefox/49.0" "-"
172.17.0.1 - - [30/Nov/2016:18:07:59 +0000] "GET /favicon.ico HTTP/1.1" 404 169 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:49.0) Gecko/20100101 Firefox/49.0" "-"
2016/11/30 18:07:59 [error] 7#7: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost"

That’s a good start, but you now have a terminal tied up with nginx, and if you hit CTRL-C in that terminal, your web-server dies.

To practice a different solution, let’s first stop this container. How to? In a new terminal, use the docker stop command!

$ docker stop nginx
nginx

Check that it’s gone:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Now let’s go ahead with using the Docker option -d to run the container in the background instead (daemon mode):

$ docker run -d -p 80:80 --name=nginx nginx
48a2dca14407484ca4e7f564d6e8c226d8fdd8441e5196577b2942383b251106

Go back to your browser, reload localhost (or <Your VM's IP Address>), and you should get the page loaded again.

We can view the logs of our nginx service with the docker logs command, followed by the container name, nginx:

$ docker logs --follow nginx
172.17.0.1 - - [30/Nov/2016:18:18:40 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:49.0) Gecko/20100101 Firefox/49.0" "-"

This gives us a live look at what is going on in our nginx container (try reloading your webpage and see what the logs look like). Note that the --follow option keeps the terminal open and will update the logs in real-time. If you omit it, docker logs would simply display the last few lines of the log file.

If you hit CTRL-C now, your container is still running, in the background. You can see this with the docker ps command:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                           NAMES
48a2dca14407        nginx               "nginx -g 'daemon off"   3 minutes ago       Up 3 minutes        443/tcp, 0.0.0.0:80->80/tcp   nginx

You can open a shell into the running container, if you wish, using docker exec. This can be handy for debugging:

$ docker exec -t -i nginx /bin/bash
root@48a2dca14407:/# 
root@48a2dca14407:/# exit   # or hit CTRL-D

Finally, let’s stop this container:

$ docker stop nginx
nginx

and then let’s remove it to avoid container name clashes in the future:

$ docker rm nginx
nginx

Start a Jupyter web server using a container

First, pull the container image jupyter/scipy-notebook.

Solution

$ docker pull jupyter/scipy-notebook
Using default tag: latest
latest: Pulling from jupyter/scipy-notebook
a48c500ed24e: Already exists 
[..]
5c52a47b5569: Pull complete 
Digest: sha256:a2518b3a15c207dbe4997ac94c91966256ac94792b09844fbdfe53015927136f
Status: Downloaded newer image for jupyter/scipy-notebook:latest

Now, how would you use this image to start a Jupyter webserver in daemon mode?

A couple of extra requirements:

  • assign the name jupyter to the container
  • map the current host working directory as /home/jovyan/data (see previous episode)
  • use the --rm flag from a previous episode, so that the container gets deleted upon exit

Some hints:

  • use the default HTTP port 80 for the host, to navigate to the corresponding page in the web browser using default syntax
  • this jupyter image wants to use port 8888 for the container
  • no command needs to be specified for that container, the default behaviour is to start the webserver

Solution

$ docker run -d --name=jupyter -v `pwd`:/home/jovyan/data --rm -p 80:8888 jupyter/scipy-notebook
a14f3c38dc24d745c470657ccdb62d77ce5a746fb37f5e02c21c978df4e156d1

Use docker logs to look for an access token string.

Solution

$ docker logs jupyter
Executing the command: jupyter notebook
[I 07:55:56.797 NotebookApp] Writing notebook server cookie secret to /home/jovyan/.local/share/jupyter/runtime/notebook_cookie_secret
[I 07:55:58.101 NotebookApp] JupyterLab extension loaded from /opt/conda/lib/python3.7/site-packages/jupyterlab
[I 07:55:58.101 NotebookApp] JupyterLab application directory is /opt/conda/share/jupyter/lab
[I 07:55:58.103 NotebookApp] Serving notebooks from local directory: /home/jovyan
[I 07:55:58.103 NotebookApp] The Jupyter Notebook is running at:
[I 07:55:58.103 NotebookApp] http://(a14f3c38dc24 or 127.0.0.1):8888/?token=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
[I 07:55:58.103 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 07:55:58.106 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///home/jovyan/.local/share/jupyter/runtime/nbserver-6-open.html
    Or copy and paste one of these URLs:
        http://(a14f3c38dc24 or 127.0.0.1):8888/?token=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv

Copy the token, in this example abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv, in the clipboard. Then use your web browser to go to localhost if you are running Docker on your machine, or <Your VMs IP Address> if you are running on a cloud service. Paste the token to access your containerised Jupyter server!

Once you are done, close the browser window, then stop the container.

Solution

$ docker stop jupyter
jupyter

Key Points

  • You-ve learned how to run long-running services (like a web server) through containers

  • Use the flag -d to run the containers in background

  • Use the flag -p <host port:<container port> to map communication ports

  • Additional options to manage and query containers include --name and docker logs