Show Notes for the Head in the Clouds YouTube Video Series

Episode 13 - Docker Jumpstart

Welcome to Episode 13 of the “Head in the Clouds” Video Series. I am Ken Hartman, a SANS Certified Instructor and content creator for the SANS Cloud Security Curriculum.

Today’s episode is titled: “Docker Jumpstart”

Many folks that work in information security know that they should learn about Docker, and keep meaning to do so, but for some reason have not entered the foray. If you fall into that category, well jump right in and follow along with this episode.

Setup

For this episode, we will assume that you have a fresh Ubuntu 20.01 virtual machine running and have a SSH connection to it. For your reference, this section follows the steps outlined in the Docker installation documentation.

First, set up the repo by running the following commands:

sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release

Then add Docker’s official GPG key

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Next, set up Docker Engine (Community Edition):

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

Explore Docker

Verify that there are no docker images on the local system:

sudo docker images

Note that if we try to run the docker images command with out sudo we get a permissions error:

ubuntu@ubuntu:~$ docker images
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/json": dial unix /var/run/docker.sock: connect: permission denied

We can fix that by adding the “ubuntu” user to the “docker” group:

sudo usermod -aG docker ubuntu
newgrp docker       # Trick to load new group permissions

Now we can run the Docker version of the classic ‘hello world’ test:

ubuntu@ubuntu:~$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:9ade9cc2e26189a19c2e8854b9c8f1e14829b51c55a630ee675a5a9540ef6ccf
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.
    (amd64)
 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://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Let’s rerun the docker images and see what changed:

ubuntu@ubuntu:~$ docker images
REPOSITORY    TAG       IMAGE ID       CREATED      SIZE
hello-world   latest    feb5d9fea6a5   8 days ago   13.3kB

Ok, great. We don’t need that image around anymore. So what command do we need to delete the image? Run docker help to see a list of all the possible docker commands.

It looks like it would be docker rmi, so let’s try it:

ubuntu@ubuntu:~$ docker rmi hello-world
Error response from daemon: conflict: unable to remove repository reference "hello-world" (must force) - container 39c0bbdce667 is using its referenced image feb5d9fea6a5

Well, that didn’t work. We can’t remove the image while there is a container referencing it. Let’s see what containers are running, using docker ps

ubuntu@ubuntu:~$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Well, that didn’t show us what we wanted to see, let’s rerun it with the --all option:

ubuntu@ubuntu:~$ docker ps --all
CONTAINER ID   IMAGE         COMMAND    CREATED              STATUS                          PORTS     NAMES
39c0bbdce667   hello-world   "/hello"   About a minute ago   Exited (0) About a minute ago             nifty_turing

The --all option shows all containers, not just the running containers. (For more info see https://docs.docker.com/engine/reference/commandline/ps/)

Now that we know the container id that is referencing our hello-world image, we can delete it using the docker rm command, as follows:

docker rm 39c0bbdce667

Now if we rerun the docker ps --all command, we see that the container is gone:

ubuntu@ubuntu:~$ docker ps --all
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Once the container is zapped, we can delete the image:

ubuntu@ubuntu:~$ docker rmi hello-world
Untagged: hello-world:latest
Untagged: hello-world@sha256:9ade9cc2e26189a19c2e8854b9c8f1e14829b51c55a630ee675a5a9540ef6ccf
Deleted: sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412
Deleted: sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359

And just to confirm that it is actually deleted:

ubuntu@ubuntu:~$ docker images
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE

Great, now let’s pull down a different image. Let’s pull down the official Ubuntu container image from Docker hub:

ubuntu@ubuntu:~$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
f3ef4ff62e0d: Pull complete
Digest: sha256:44ab2c3b26363823dcb965498ab06abf74a1e6af20a732902250743df0d4172d
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

We can see that it was downloaded:

ubuntu@ubuntu:~$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
ubuntu       latest    597ce1600cf4   46 hours ago   72.8MB

Next, let’s run the container in an interactive mode so that we execute various commands from the bash shell within the container. To do that, we need two options:

While we are at it, we will assign a name to the container with the --name option.

So our command becomes:

docker run --name test -it ubuntu bash

And then we see our prompt change. Now we can run some commands that are executed inside the container:

ubuntu@ubuntu:~$ docker run --name test -it ubuntu bash
root@a843c11e5313:/# whoami
root
root@a843c11e5313:/# hostname
a843c11e5313
root@a843c11e5313:/# pwd
/
root@a843c11e5313:/# ls /
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@a843c11e5313:/# ls /home
root@a843c11e5313:/# exit
exit
ubuntu@ubuntu:~$

The exit command kills the container executing in the foreground and returns us to our original prompt.

List the container:

ubuntu@ubuntu:~$ docker ps --all
CONTAINER ID   IMAGE     COMMAND   CREATED         STATUS                     PORTS     NAMES
a843c11e5313   ubuntu    "bash"    4 minutes ago   Exited (0) 3 minutes ago             test

We can run the container again by using the docker start command, remembering to pass in the interactive option:

ubuntu@ubuntu:~$ docker start -i test
root@a843c11e5313:/#
root@a843c11e5313:/# exit
exit
ubuntu@ubuntu:~$

If we use the --rm option with the run command, it cleans up the container when the command passed into the container exits:

docker run --name test2 -it --rm ubuntu ls

In this case, the ls command runs in the container, and then the container exits:

ubuntu@ubuntu:~$ docker run --name test2 -it --rm ubuntu ls
bin   dev  home  lib32  libx32  mnt  proc  run   srv  tmp  var
boot  etc  lib   lib64  media   opt  root  sbin  sys  usr
ubuntu@ubuntu:~$

Note that we named the container “test2” but when we run docker ps --all we do not see it listed, because of the --rm switch.


ubuntu@ubuntu:~$ docker ps --all
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS                         PORTS     NAMES
a843c11e5313   ubuntu    "bash"                   2 hours ago      Exited (0) About an hour ago             test

Create a Custom Image

The most common use case for docker is to run web services, so let’s create a simple web server to illustrate some important concepts. First, lets create a basic web page called “index.html” in a directory called “html”

mkdir html
echo "Head in the Clouds" > html/index.html

With that done, we need to create a Dockerfile

Images are typically built from a base image adding and removing stuff as needed. Our base image will be ‘nginx’ since it already has the web server installed. Then we will copy our “html” folder onto the image stomping on the existing /usr/share/nginx/html

echo 'FROM nginx' > Dockerfile
echo 'COPY html /usr/share/nginx/html' >> Dockerfile

That creates our Dockerfile, thanks to the magic of file redirection. Double-checking our Dockerfile:

ubuntu@ubuntu:~$ cat Dockerfile
FROM nginx
COPY html /usr/share/nginx/html

Now let’s build a custom image based on our Dockerfile:

docker build -t hitc .

Note that the “.” indicates “the current directory”

ubuntu@ubuntu:~$ docker build -t hitc .
Sending build context to Docker daemon  14.85kB
Step 1/2 : FROM nginx
latest: Pulling from library/nginx
07aded7c29c6: Pull complete
bbe0b7acc89c: Pull complete
44ac32b0bba8: Pull complete
91d6e3e593db: Pull complete
8700267f2376: Pull complete
4ce73aa6e9b0: Pull complete
Digest: sha256:765e51caa9e739220d59c7f7a75508e77361b441dccf128483b7f5cce8306652
Status: Downloaded newer image for nginx:latest
 ---> f8f4ffc8092c
Step 2/2 : COPY html /usr/share/nginx/html
 ---> 995b0de5245b
Successfully built 995b0de5245b
Successfully tagged hitc:latest
ubuntu@ubuntu:~$

Now when we run docker images we see two new images along with the ‘ubuntu’ image from before:

ubuntu@ubuntu:~$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
hitc         latest    995b0de5245b   44 seconds ago   133MB
ubuntu       latest    597ce1600cf4   47 hours ago     72.8MB
nginx        latest    f8f4ffc8092c   4 days ago       133MB

The “hitc” is the image that we just created, and it is derived from the “nginx” image referenced in the Dockerfile. Because it was referenced, Docker pulled down a local copy from Docker Hub.

Ok, lets spin up a container from this image and see if we can hit with curl:

docker run --name episode13 -d -p 8080:80 hitc

And we get:

ubuntu@ubuntu:~$ docker run --name episode13 -d -p 8080:80 hitc
61747901bb594c88ec03736a50886985b404aca4a4a3d85b2b9608d190a8c626
ubuntu@ubuntu:~$ curl localhost:8080
Head in the Clouds
ubuntu@ubuntu:~$

Note that the -d option ran the container in the background and the -p option maps port 8080 on the host to port 80 in the container.

We can run docker ps to see that it is still running in the background:

ubuntu@ubuntu:~$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                                   NAMES
61747901bb59   hitc      "/docker-entrypoint.…"   5 minutes ago   Up 5 minutes   0.0.0.0:8080->80/tcp, :::8080->80/tcp   episode13

If we want to see what processes are running inside the container, we can use docker top [CONTAINER] like so:

ubuntu@ubuntu:~$ docker top episode13
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                10642               10616               0                   01:47               ?                   00:00:00            nginx: master process nginx -g daemon off;
systemd+            10693               10642               0                   01:47               ?                   00:00:00            nginx: worker process

If we stop the container, we will no longer be able to connect to port 8080:

ubuntu@ubuntu:~$ docker stop episode13
episode13
ubuntu@ubuntu:~$ curl localhost:8080
curl: (7) Failed to connect to localhost port 8080: Connection refused

Wrap up

Well, there you go. We installed Docker Engine Community Edition, learned some basic Docker commands, and launched a very basic website. Hopefully this video has removed any reasons you may have had to procrastinate and inspired you to jump in and play. Of course there is much more to learn and many additional tutorials that are just an internet search away. In our next episode, we will build on this foundation and create a Docker image that will have utility for members of this audience as we continue to explore the cloud.

If you have thoughts or comments on today’s episode, feel free to chime in on the comments for this YouTube video.

If you appreciate this video and want to see more like it, be sure to give it a “thumbs up.”

Stay tuned for another installment of “Head in the Clouds” as announcements of new episodes are made on the SANS Cloud Security Twitter feed.

Meanwhile, be sure to check out the other great videos on the SANS Cloud Security YouTube Channel.

Take care.