“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:
pi
user to the docker
groupcgroups
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.
As the answer to the first exercise, please list the names of the people who worked together on this studio.
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
.
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.
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:
Explain what the --rm
flag, passed to the docker run
command, accomplishes.
HINT: refer to the Docker run reference
Inside of your container, run the following commands, and show their output:
ls
ps
id -u
id -g
The docker ps
command allows you to view Docker containers that are running on a system.
In a separate terminal window (i.e., outside of the container), run the command, and show its output.
Run df -a
from both inside and outside the container, and show its output in both places.
Explain what this tells you about where the container's root filesystem resides from the perspective of the global system namespace.
In the container, run ls -l /
Outside of the container, navigate to the directory that holds the container's root filesystem,
then run ls -l
from there
(you may need to launch a root shell to do so).
Show the output of both commands.
Once you are done, exit the container.
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.
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.
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.
alpine-gcc
image.Page updated Tuesday, March 1, 2022, by Marion Sudvarg and Chris Gill.