Lab 10 - Special Topics

Docker

This exercise is designed to give you some hands-on experience with Docker! By the end of this assignment, you should be able to:

Installing Docker

One of the annoying things about Docker is it’s usually not in apt and if it is, it’s always under a different name. Here’s a link to the .deb for Docker Community Edition you can download and install on your VM: https://download.docker.com/linux/debian/dists/stretch/pool/stable/amd64/docker-ce_17.06.1~ce-0~debian_amd64.deb

wget https://download.docker.com/linux/debian/dists/stretch/pool/stable/amd64/docker-ce_17.06.1~ce-0~debian_amd64.deb
sudo dpkg -i docker-ce_17.06.1~ce-0~debian_amd64.deb

It’ll complain about dependencies or something, so just run the following command to wrap up

sudo apt-get install -f

After installing, I recommend following these instructions so you can use docker as a non-root user. This means you won’t have to type sudo docker all the time. This is optional but for the rest of this exercise I’m going to assume that you did this. You might see some weird output like

sent invalidate(passwd) request, exiting
sent invalidate(group) request, exiting

But it should work anyway.

Creating your first Docker container

To verify that you installed things correctly, try running

docker run hello-world

You should see some friendly output like so:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b04784fba78d: Already exists 
Digest: sha256:f3b3b28a45160805bb16542c9531888519430e9e6d6ffc09d72261b0d26ff74f
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://cloud.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

Some quick definitions from Docker’s website:

An image is a lightweight, stand-alone, executable package that includes everything needed to run a piece of software, including the code, a runtime, libraries, environment variables, and config files.

A container is a runtime instance of an image—what the image becomes in memory when actually executed. It runs completely isolated from the host environment by default, only accessing host files and ports if configured to do so.

Be sure to read through the output from running the hello-world image to get an understanding of what the Docker daemon was doing.

Running an interactive container

We’re now going to walk you through running a container interactively. This is useful if you ever need to play around and install stuff on a bare system without messing up your current system. Try running the following command:

docker run -it ubuntu:latest

The -i flag tells docker to keep STDIN open to your container, and the -t flag allocates a pseudo-TTY for you. Basically you need both for you to have a shell into your newly started container. Try installing some packages from apt or just play around. It should look like a bare Linux system.

You can exit the container with CTRL+D.

Questions

  1. What user are you logged in as by default?
  2. If you start and then exit an interactive container, and then use the docker run -it ubuntu:latest command again, is it the same container? How can you tell?

Dockerfiles

A more powerful way to interface with Docker is by using a Dockerfile. A Dockerfile allows you to define an image by specifying all of the commands you would type manually to create an image. Docker can then build images from a specified Dockerfile. These Dockerfiles can be put into version control and the images distributed as a binary to keep track of both how the image is constructed and also to keep pre-built images around.

Dockerfiles are very powerful and have many different commands and features. We’ll go over a basic example, but you should check out the reference page if you are trying to do anything more complex.

Here is an example Dockerfile that will build an image that has python3.6 installed. It will also run python3.6 directly, so you’ll be at a python prompt instead of a bash prompt when you run it.

FROM ubuntu:zesty

RUN apt-get update && apt-get install -y python3.6 --no-install-recommends

CMD ["/usr/bin/python3.6", "-i"]

Note: there are some “best practices” for writing Dockerfiles that the above example doesn’t use, because it’s a basic example. If you’re interested in this stuff, check out this article.

What is this doing? We specify a base image ubuntu:zesty (the zesty version of ubuntu). We then specify that we should run (RUN) the command apt-get update and then apt-get install python3.6 so we can install python3.6 (note that this package probably isn’t in apt on your own VMs!). Then we set the default command (CMD) of the container to run the python3.6 interpreter in interactive mode.

Copy the contents of the Dockerfile above into a file named Dockerfile. Then use Docker to build it with the following command:

docker build -t mypython:latest .

This tells Docker to look in the current directory for a Dockerfile to build, and build it. The -t flag tells it to tag this build with the name mypython:latest. Docker will look for a Dockerfile in the current directory since you specified .

You can see all of the images you’ve built on your machine with the docker images command.

Questions

  1. Run the image you just built. As before, don’t forget the -it flags, and you can exit using CTRL+D. What do you observe?
  2. Write and build a Dockerfile that installs the packages fortune and fortunes-min and runs the fortune executable (located in /usr/games/fortune after you install it). Note that you won’t need to use the -it flags when you run the container as fortune doesn’t need STDIN. Submit your Dockerfile with this lab. Hint: if you’re having trouble writing your Dockerfile, try booting an interactive container and installing both packages. How can you translate what you did interactively to a Dockerfile?
  3. Paste the output of your docker images command after questions 1 and 2.

Dockerizing a Web Application

For our last trick, we’re going to take a rusty old Django web application and Dockerize it! By this, I mean that we are going to package the application and its dependencies within a Docker image. We can then distribute this image and/or run it as a container without changing our host system.

For simplicity I did the steps of making the Dockerfile and web application for you. Be sure to take a look at the Dockerfile anyway, as you should be able to understand (at a high level) what each line is doing by this point in the course. Now, it’s your job to figure out how to run it successfully on your machine.

Get started by cloning the repo: git clone https://github.com/upenu/django-polls-example and cd into the directory.

Build the Dockerfile into an image and tag it with mydjango:latest. Refer to the previous examples if you don’t know what commands to use.

Docker creates a separate network for containers, so you will need to do forward your host port to your container’s port (this is called port forwarding, or port mapping). The container is listening on port 80, so let’s try to forward our host machine’s port 5050 to the container’s port 80 when we run the container:

docker run -p=5050:80 mydjango:latest

Don’t expect to see any output until you visit the web application from your host machine. The -p flag takes in a colon separated pair of HOST_PORT:CONTAINER_PORT (it can actually accept a ton of more options, but don’t worry about that for now). You should be able to view the web application from your host machine by visiting <url_of_host_machine>:5050, assuming you don’t have anything else running on that port.

Use CTRL+C to exit the container this time.

Cool. So we can now run this application within a container successfully. You can also try running it in detached mode using the -d flag:

docker run -d -p=5050:80 mydjango:latest

The web application should still be available, but the container is now running in the background. To view the status of containers running on your system, use the docker ps command. To stop this container, use docker stop <container_id>. You can find the container ID from the output of docker ps.

Congratulations! You’ve successfully Dockerized and ran a web application with vastly different requirements and configuration than your host machine, without affecting your setup on your machine :) There’s a lot more about Docker and containers to learn about, but I hope this was enough to wrap your head around the basic concepts and get some experience working with it.

For further reading, I recommend just reading the official documentation so you can see what is possible with the Docker container format.

Questions

  1. Paste your output of docker ps after you run the container in detached mode
  2. What are the advantages of using containers compared to virtual machines? (look at the reading!!)