This lesson is in the early stages of development (Alpha version)

Pulling and Running Docker Containers

Overview

Teaching: 20 min
Exercises: 15 min
Questions
  • What is Docker?

  • How do I fetch and run Docker images?

  • How can I use folders from my local filesystem inside a Docker container?

Objectives
  • Pull and run docker images from public registries.

  • Run commands inside a container.

  • Clean up disk space by removing leftover containers.

  • Mount folders from the local file system into the Docker container.

Brief Introduction into Docker

At the time of writing Docker is the most popular and most widely used container solution on the market. Please refer to the Setup section for installation instructions for your OS.

Verify the Docker installation

$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

If you get a Permission Denied error message, your user account is probably not added to the docker group. In this case you need to either prepend the sudo command or add your user to the docker user group.

Basic Docker Commands

Now that everything is set up, it is time to issue the first docker commands. Let’s pull our first image from Dockerhub. As a start we want to pull this Python image.

$ docker pull python
Using default tag: latest
latest: Pulling from library/python
e79bb959ec00: Pull complete
d4b7902036fe: Pull complete
1b2a72d4e030: Pull complete
d54db43011fd: Pull complete
69d473365bb3: Pull complete
7dc3a6a0e509: Pull complete
68cd774d0852: Pull complete
2ef86095a118: Pull complete
bd9da5a171e0: Pull complete
Digest: sha256:67a2befe73bf0233d066496f40297602fcf288858641cc8843fb5224a2b29339
Status: Downloaded newer image for python:latest

The docker pull command fetches the python image from a Docker registry and saves it locally to our system. Use the command docker images to see a list of all images on your system.

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
python              latest              954987809e63        4 days ago          929MB

Let’s now run a Docker container based on the python image. To do that we use the command docker run:

$ docker run python
$

Nothing really seemed to happen this time. This is not a bug. Many things were going on behind the scenes: When you call the command docker run, the docker client finds the image, loads up the container and runs a command inside the container. We ran docker run python without providing a command and thus it directly exited again without producing output. Let’s try again by providing a command to docker run.

$ docker run python python --version
Python 3.7.3

We now ran the command python --version in the docker container and, as expected, the python version number was printed out to the terminal. Again, once the command finished the container exits. The general pattern for running commands in a docker container is docker run [options] image-name [command] [arguments].

Hopefully, you noticed that all of this happened pretty quickly. Imagine you needed to boot up a virtual machine, run the command and destroy the VM. That’s the speed difference mentioned in the introduction. The docker ps command shows you all containers that are currently running.

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

As expected, since no containers are running, we see a blank line. Use docker ps -a to get a more complete output:

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                      PORTS               NAMES
3289eb90dd12        python              "python --version"   7 seconds ago       Exited (0) 6 seconds ago                        hungry_blackwell
70b36daceef2        python              "python3"            12 seconds ago      Exited (0) 11 seconds ago                       flamboyant_hodgkin

You might be wondering now if there is a way to run more than one command in a container. Invoking the run command with the -it flag will give you an interactive tty session in the container. Then you can run as many commands in the container as you want to, like in a normal bash or Python interpreter.

$ docker run -it python python3
Python 3.7.3 (default, Mar 27 2019, 23:40:30)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
$ docker run -it python bash
root@4d81391e2369:/#

Interestingly, we also get an interactive Python interpreter by only running the command docker run -it python. Keep that in mind, we will see later why this works in that case.

So far, we have had no access to files from the local filesystem inside the Docker container. But you probably want to have exactly that. With Docker, you can easily mount folders inside a container. Supply the -v option when running the docker run command and as an argument pass the absolute path on the local filesystem on the left side and on the right side the absolute path inside the container. Split both paths with a :. You can use -v multiple times in one docker run command. The command below mounts the directory test from the users home-directory to the path /root/test inside the container.

$ docker run -v $HOME/test:/root/test --rm -it alpine:latest sh
/ # cd /root/test
/ # touch foo
/ # exit
$ cd $HOME/test
$ ls
foo

Because of permissions and user IDs, it might happen, that you cannot access the files, that were written by the application within Docker. Docker uses the root-user if not specified differently somewhere else, which means, for example, that files, directories, and packages that were written or installed within the container would have user and group root as the default. This may result in an access denied error message if you try to access those artifacts with a non-root user. To avoid that, you have to pass you local user ID to Docker:

-u $(id -u ${USER}):$(id -g ${USER})

docker run --rm -it
       -u $(id -u ${USER}):$(id -g ${USER})
       -v $HOME/test:/root/test
       alpine:latest
       sh

What about the --rm option though? You might have already noticed that we can still see leftover containers from previous runs that even exited by running docker ps -a. Throughout this lesson you will run docker run multiple times. This will leave containers which in turn will occupy disk space. It is necessary to regularly delete old containers from the disk. To do that you can run the docker rm command. Copy the container IDs from the output of docker ps -a and paste them alongside the command.

$ docker rm 3289eb90dd12 70b36daceef2
3289eb90dd12
70b36daceef2

This can be a repetitive task. You can delete a bunch of containers in one go as shown in the following command.

$ docker rm $(docker ps -a -q -f status=exited)

This command deletes all containers that are in the exited status. Here, the option -f filters the output based on the conditions provided and -q only returns the numeric IDs. Starting with Docker version 1.13 you can also use the command docker container prune. This will achieve the same result as above command.

One last useful thing: Combine the docker run command with the option --rm as was shown before. This will automatically delete the container once it exits from it. But be careful, this is only useful, if you want to run the container only once. It is definitely gone afterwards and will have to be re-downloaded if you wish to run it again.

Docker Volumes

We have already seen how to mount folders from the host to the container. Volumes are the only way to have persistent data within a container and the reason why so many containers (like databases) use them. Because often it is not needed to access the files from the host system, we can let Docker manage this persistent storage.

docker volume ls

With this command we can list all existing volumes.

docker volume create test_volume

Here we created a volume called test_volume.

$ docker volume ls
DRIVER    VOLUME NAME
local     test_volume

Volumes are particularly useful because they are OS independent and fully managed by the Docker daemon. Some additional advantages of volumes:

In the end, volumes are names for folders on the host (like / or ~). We can see their location on the host, if we inspect a volume.

$ docker volume inspect test_volume
[
    {
        "CreatedAt": "2021-03-22T07:54:10+01:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/test_volume/_data",
        "Name": "test_volume",
        "Options": {},
        "Scope": "local"
    }
]

We can mount a volume like a folder from a host system by using its name on the left side of the : in -v argument.

docker run -it \
       -v test_volume:/test \
       alpine:latest \
       touch /test/foo

After a volume is unmounted, we can use the following command to remove it.

docker volume rm test_volume

Note: All containers associated with a volume need to be removed beforehand.

Run your own Docker image

Your goal in this exercise is to run a Jupyter notebook inside a Docker container. You need to be able to open the Jupyter notebook in the browser of the host system and run Python commands inside. Delete the created container afterwards and make sure, that there are no leftover containers.

  1. Find an appropriate image from Dockerhub and pull it.
  2. Create a folder called jupyter on your local system.
  3. Run a jupyter container basing on the image you just pulled. Bind the port 8888 to localhost. Note the hint below.
  4. Open Jupyter notebook in the browser of your host system: http://localhost:8888
  5. Stop the container and delete the container image.

Hint: Use the option -p 127.0.0.1:8888:8888 to bind the Jupyter notebook port to your host system.

Solution

We use the jupyter/datascience-notebook from Dockerhub. Start the container and open your browser at http://localhost:8888

$ docker run -p 127.0.0.1:8888:8888 \
>      --name jupyter \
>      -v $HOME/test:/home/jovyan/mnt \
>      jupyter/datascience-notebook
Executing the command: jupyter notebook
[I 06:16:47.141 NotebookApp] Writing notebook server cookie secret to /home/jovyan/.local/share/jupyter/runtime/notebook_cookie_secret
[I 06:16:48.461 NotebookApp] JupyterLab extension loaded from /opt/conda/lib/python3.7/site-packages/jupyterlab
[I 06:16:48.461 NotebookApp] JupyterLab application directory is /opt/conda/share/jupyter/lab
[I 06:16:48.464 NotebookApp] Serving notebooks from local directory: /home/jovyan
[I 06:16:48.464 NotebookApp] The Jupyter Notebook is running at:
[I 06:16:48.464 NotebookApp] http://(2d15068040fa or 127.0.0.1):8888/?token=f69199c29b6b3421c878fb40c335246d608430bc08dcfaad
[I 06:16:48.464 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 06:16:48.468 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://(2d15068040fa or 127.0.0.1):8888/?token=f69199c29b6b3421c878fb40c335246d608430bc08dcfaad
$ docker rm jupyter
jupyter
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Key Points

  • Use docker pull to fetch images from a Docker registry and to save them locally.

  • docker run creates a container from a Docker image and runs a command inside.

  • Use the option -v with the docker run command to mount a local folder into the docker container.

  • To cleanup leftover containers use the command docker rm.