CSE 522S: Studio 11

Introduction to Docker Containers and Images

“A Docker container is a Linux container that has been instantiated from a Docker image. A specific container can only exist once; however, you can easily create multiple containers from the same image.”

—Sean P. Kane & Karl Matthias, Docker Up & Running, 2nd Edition

Docker is a fully-featured container environment that automates the creation of containers, and allows the monitoring and management of multiple containers, even across multiple hosts.

In this studio, you will:

  1. Install Docker, and add the pi user to the docker group
  2. Launch a container based on the minimal Alpine Linux distribution image
  3. Create your own image to encapsulate a simple application running on top of Alpine Linux
  4. Explore how Docker's container environment uses cgroups and namespaces similarly to the simple container environment in previous exercises.

Please complete the required exercises below. We encourage you to please work in groups of 2 or 3 people on each studio (and the groups are allowed to change from studio to studio) though if you would prefer to complete any studio by yourself that is allowed.

As you work through these exercises, please record your answers, and when finished upload them along with the relevant source code to the appropriate spot on Canvas.

Make sure that the name of each person who worked on these exercises is listed in the first answer, and make sure you number each of your responses so it is easy to match your responses with each exercise.

Required Exercises

  1. As the answer to the first exercise, please list the names of the people who worked together on this studio.

  2. To begin, you will need to install Docker on your Raspberry Pi. The Docker client and server are bundled together in the APT package docker.io. Install it by running the command:

    sudo apt-get update
    sudo apt-get install docker.io

    The Docker server, as installed, should run automatically when your Raspberry Pi boots. To make sure of this, though, you will manually add it as a startup service to systemd:

    sudo systemctl enable docker
    sudo systemctl start docker

    To verify that Docker installed correctly, and that the server is now running, issue the following two commands:

    sudo docker version
    ps aux | grep docker

    As the answer to this exercise, please say (1) the Client version that was installed, (2) the version for the Engine, containerd, runc, and docker-init server components that were installed, and (3) the location of the Docker server daemon executable, as reported by ps.

  3. Try running docker version without sudo. You should observe that only the Client version is reported; by default, non-root users are restricted from access to the Docker server.

    Docker establishes a group, aptly named docker, to which its members are conferred additional privileges for launching and managing Docker containers. Add the pi user to this group with the following command:

    sudo usermod -a -G docker pi

    Now, verify that the user has been added to the group by issuing the following command:

    getent group docker

    In Linux, a user can view their own group membershp with the command groups. Run this command. You might not see the docker group listed, in which case you will need to log out and back into your user session to have the session's group membership updated. After doing so, run the groups command again to verify that docker is listed.

    As the answer to this exercise, please list the output of the getent and groups commands that you ran.

  4. Now, you are ready to start using Docker! We'll begin with launching a container from a simple image. Alpine Linux is a minimal Linux distribution that uses the lightweight musl c library instead of glibc, and has a minimal set of shell utilities and mounted files and directories to enable interaction via the host Linux kernel.

    Alpine Linux is available as an image in the Docker Hub, which is Docker's public registry for distributing container images.

    Run an Alpine Linux container by issuing the following command:

    docker run --rm -ti alpine:latest /bin/sh

    For interactive processes, e.g. those which provide a shell, you must use the -ti flags to allocate a tty for the container process. The -t flag allocates a pseudo-tty, and the -i flag keeps stdin open for the tty.

    The /bin/sh specifies which command should be run in the container when it is launched. This launches the sh shell, a lighter shell environment than bash.

    As the answer to this exercise, please provide the following:

    Once you are done, exit the container.

  5. A Docker image is intended to encapsulate an application, including all dependencies it requires to run on the Linux kernel. Docker images are constructed in layers, defined in a Dockerfile, and a layer can refer to a base image from which it inherits. To create an image that encapsulates a program, you will create a Dockerfile that inherits, as its first layer, from the Alpine Linux container image you used in the previous exercise.

    The next layer could consist of a compiled binary of a program; however, images are intended to be portable across achitectures. Inserting a binary executable compiled on the Raspberry Pi would mean that the image could not be used to run containers on, e.g., Intel platforms.

    Images allow you to add layers that specify a command that runs in the container as it starts. So, in an image specification (the Dockerfile), you can specify one or more layers that compile the programs encapsulated by the container. Unfortunately, Alpine Linux does not come with a built-in C compiler. Luckily, though, it does come with a package manager, which can be used to install gcc.

    For this exercise, you will create a new Docker image, called alpine-gcc, that will inherit from the Alpine Linux image as a base layer, then a second layer will install gcc. Create a new directory on your Raspberry Pi, in which you will keep your Docker image specifications and files. In that directory, create a directory called alpine-gcc.

    Inside the alpine-gcc directory, create a new file, simply called Dockerfile. That file should have the following lines:

    FROM alpine:latest

    (This establishes a layer that inherits the Alpine Linux image.)

    RUN apk add gcc musl-dev

    (This installs gcc and the musl-dev C library.)

    Now, still in the alpine-gcc directory, build the image using the following command:

    docker build -t alpine-gcc:v0 .

    You can replace v0 with any release tag that you would like to use.

    Now, launch a container from your new image with the following command:

    docker run --rm -ti alpine-gcc:v0 /bin/sh

    Verify that the container has gcc installed by running gcc. Of course, with no program to provide as input, it should report an error and terminate compilation; however, the gcc should still be found.

    As the answer to this exercise, please show (1) the output of the docker build command, and (2) the output of the gcc command from inside your container.

    When you are done, exit the container.

  6. Now that you have an image of Alpine Linux with gcc, you are ready to package an application!

    Create a new directory for this image, and navigate into that directory. First, create a subdirectory called app. This will hold your application's code. In that directory, create a simple program (for our example, we will call it hello-world.c) that (1) prints a simple message to the terminal, then (2) spins indefinitely in a loop.

    Compile and run your program, just to verify that it works as expected. After running it, delete the compiled binary. This will be compiled by the container.

    Outside of the app subdirectory (but still in the directory you created for this image), create a new Dockerfile. The first line should inherit from the alpine-gcc image you created in the previous exercise. Use the following for the remaining lines:

    COPY app /app

    (This copies the app directory into the /app root directory of the container)

    RUN gcc /app/hello-world.c -o /app/hello-world

    (This instructs the container to run gcc to compile the program)

    CMD /app/hello-world

    (This specifies a command to run when the container launches)

    Now, build your container:

    docker build -t mycontainer:v0 .

    (Replace mycontainer with the name you would like to use for your container)

    Since your image already specifies a command, you can run it with:

    docker run --rm -ti mycontainer:v0

    With your container running, open another terminal window. Use the ps aux command to retrieve the PID of the process running your application in the container.

    As the answer to this exercise, please show:
    (1) the output of the docker ps command
    (2) the contents of the /proc/PID/cgroup file (where PID is the PID you retrieved with ps),
    and (3) the output of the commands ls -li /proc/self/ns and ls -li /proc/PID/ns (you may need to run the second one from a root shell). What does this tell you about the cgroup and namespace membership of the container?

    Keep your container running for the next exercise.

  7. For the final exercise of today's studio, you will export and inspect the files from your running container.

    To export your container, use the container ID as reported by the docker ps command. It should be a 12-character hexadecimal string.

    Now, run the command:

    docker export fedcba987654 -o mycontainer.tar

    Replace fedcba987654 with the container ID.

    Now, look at the contents of the file with the following command:

    tar -tvf mycontainer.tar | less

    tar -tvf allows you to inspect the contents of a tar archive, and piping the output into less provides a scrollable view that fits in your terminal window. Take a look at the files, and get some sense of what's in there.

    As the answer to this exercise, please (1) report how many files are in the container (e.g., by piping tar -tvf mycontainer.tar into the wc wordcount utility), then (2) report the size of the container (i.e., the size of the exported tar file). Please also (3) say how that compares to the size of the original program you wrote.

  8. Things to Turn In:

Page updated Tuesday, March 1, 2022, by Marion Sudvarg and Chris Gill.