In Part 2a, we covered the Docker installation and verification steps that I use to set things up.  In this part we'll go ahead and start some containers while reviewing some basic Docker commands.

The Fun with Docker Series

Links to the entire series are here:

Your first Docker container and some basic commands

I'm not going to spend too much time on Docker commands for a couple of reasons:

  • I prefer to use Docker Compose to manage/run things, which is something I will cover in the next post.
  • There are already so many good Docker command resources out there that can cover things in much more detail (and expertise) than I can.  Here is a good example.  Another good one is here.

Let's run our first container - Like any stereotypical nerd test, it's called "Hello World" and is an image posted on the Docker Hub (a public repository of Docker images) that can be used for testing:

Note: Without getting too deep into the weeds, Raspbian running on Raspberry Pis uses the armhf architecture, so any Docker Hub images you run must be built for that architecture.  This includes A LOT of good packages, however, and Docker will make sure things work before grabbing the images.  Ubuntu systems are typically arm64 or x86-64 architectures, and again, Docker will make sure to grab the appropriate images.
docker run --rm hello-world

A couple of things will happen here; paraphrased from the output that you'll get:

  • Docker will look for a local copy of the hello-world image
  • If it's not found, Docker will download the image from the Docker Hub
  • Docker will start the hello-world container based on the downloaded image
  • The container will output some stuff and then stop
  • Docker will remove the downloaded image once it stop because we used the --rm option

Here's what you should see depending on which system you're running it on:

pi@raspberrypi:~ $ docker run hello-world

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.
    (arm32v7)
 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/

Obviously this isn't a typical Docker use-case since the container self-destructed after it finished running and was simply meant to produce some verification output, but it is important to understand that the whole thing actually did run inside of a container and not on the host OS natively.

Now let's take their "ambitious" suggestion and run another command:

docker run -it ubuntu bash

This command will actually download and create an Ubuntu container, run bash inside of it, and then dump you right to the newly created container's CLI via the -it option:

pi@raspberrypi:~ $ docker run -it ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
5379ca036368: Pull complete 
4ede4c7641a5: Pull complete 
0994f5ac8c79: Pull complete 
a81b96316730: Pull complete 
Digest: sha256:c303f19cfe9ee92badbbbd7567bc1ca47789f79303ddcef56f77687d4744cd7a
Status: Downloaded newer image for ubuntu:latest
root@5ac5b177b7f3:/# echo "$(. /etc/os-release; echo "$ID")"
ubuntu
root@5ac5b177b7f3:/# uname -a
Linux 5ac5b177b7f3 4.19.60-v7l+ #1247 SMP Thu Jul 25 14:54:07 BST 2019 armv7l armv7l armv7l GNU/Linux

Oh, hey look! We just spun-up an isolated Ubuntu image on our Raspberry Pi that we can mess around with. All without installing anything new in Raspbian, dealing with the hassle of downloading a huge OVA file, running an installer, or messing with any build tools. I threw in a couple of commands at the end to verify this voodoo magic.

At this point, you could even use apt inside the new container and install any Ubuntu packages that you want to test with, without cluttering up your host system.

If we login to the Raspberry Pi on another tty and issue the docker ps command, we will see the Ubuntu container running (yes, the line-wrapping is getting annoying):

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
eac1e3679afd        ubuntu              "bash"              20 seconds ago      Up 19 seconds                           confident_bell

We can simply type exit to get back to our Raspberry Pi. This will also stop the container, while still keeping the image downloaded for faster execution the next time. Like so:

root@eac1e3679afd:/# exit
exit
pi@raspberrypi:~ $ docker run -it ubuntu bash
root@b7c84a5892e6:/# exit
exit
pi@raspberrypi:~ $

Because the container is no longer running, the output of a docker ps won't show anything, but if we use the -a option with the command, we can see all created containers, even the ones that aren't running (!@#$ line-wrapping):

pi@raspberrypi:~ $ docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                       PORTS               NAMES
eac1e3679afd        ubuntu                "bash"                   2 minutes ago       Exited (0) 3 seconds ago                         confident_bell
b7c84a5892e6        ubuntu                "bash"                   7 minutes ago       Exited (127) 7 minutes ago                       musing_cartwright

Intermission: While writing this section, I got really frustrated with the default line-wrapping behavior in Docker and fell down a rabbit hole (this happens often) to try to find a solution. I discovered this thread, and learned a couple of things:

  • you can pipe Docker commands through less -S to make things look a little better, while still being able to use your arrow keys to see all of the output
  • you can use a the --format option to select only the columns that you want to display. Like this:
pi@raspberrypi:~ $ docker ps -a --format="table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"
CONTAINER ID        NAMES               IMAGE               STATUS
eac1e3679afd        confident_bell      ubuntu              Exited (0) 23 minutes ago
b7c84a5892e6        musing_cartwright   ubuntu              Exited (127) 31 minutes ago
5ac5b177b7f3        pensive_joliot      ubuntu              Exited (0) 31 minutes ago

Now back to our regularly scheduled programming.

A couple other commands

Let's get fancy for a minute so that we can cover two other important Docker commands: docker start and docker stop. In this example we're going to do the following:

  • download and start a new image and container (CentOS this time, because why not?)
  • have the CentOS container run a continuous ping so that it stays running
  • verify that the container is running using the docker ps command (now with less line-wrapping!)
  • stop the container
  • verify that the container is stopped
  • start the container again
  • verify that the container is running again

I'll explain the options I'm using in the docker run command after the example:

pi@raspberrypi:~ $ docker run --name centos-linux -d centos /bin/sh -c "while true; do ping 8.8.8.8; done"
Unable to find image 'centos:latest' locally
latest: Pulling from library/centos
193bcbf05ff9: Pull complete 
Digest: sha256:a799dd8a2ded4a83484bbae769d97655392b3f86533ceb7dd96bbac929809f3c
Status: Downloaded newer image for centos:latest
363c5d35e51067d0f1879627945f39c49802bc0d634f03a788a69d7be277ead8
pi@raspberrypi:~ $ docker ps --format="table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"    
CONTAINER ID        NAMES               IMAGE               STATUS
363c5d35e510        centos-linux        centos              Up 27 seconds
pi@raspberrypi:~ $ docker stop centos-linux
centos-linux
pi@raspberrypi:~ $ docker ps --format="table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"
CONTAINER ID        NAMES               IMAGE               STATUS
pi@raspberrypi:~ $ docker start centos-linux
centos-linux
pi@raspberrypi:~ $ docker ps --format="table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"
CONTAINER ID        NAMES               IMAGE               STATUS
363c5d35e510        centos-linux        centos              Up 3 seconds

The above exercise may seem silly and pointless, but it's useful to illustrate the amount of control you have over containers, running or not, and how easy it is to manage them.

The docker run command we issued above does a few things:

  • --name centos-linux gives the container a name so that we can manipulate the container easier and avoid the random goofy names like "musingcartwright" that Docker creates
  • -d specifies that the container should run detached or in the background
  • centos is the image name that Docker should download and run
  • /bin/sh -c.... is the looped ping command that will run inside of the container

Cleaning things up

Something to keep in mind as you start playing with Docker is that downloaded images can take up a lot of space on the host over time; especially if you're constantly testing new ones.  It's a good idea to do some periodic maintenance on both non-running containers and images (and also Volumes, but that will be covered in a future post).

The first thing we should do is to delete any non-running containers (you can always start create them again with the docker run). Remember above we had three Ubuntu containers that were no longer running. We can clean those up in one command:

pi@raspberrypi:~ $ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
eac1e3679afdef434c0bb182c8af1d9db6ea86dd1135ba363a94d12cd51a994c
b7c84a5892e6b2087f59ed1a5e6f8414f44ee0efe058599c0b712b26871f0462
5ac5b177b7f33344fe3b71227f816de40b4f5a602a16e19d8cd6362ef1d94040

Docker images should also be cleaned-up periodically. The first command below will list all downloaded images, and the second one will delete any unused images.

pi@raspberrypi:~ $ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              6348795f7982        2 weeks ago         46.7MB
pi@raspberrypi:~ $ docker image prune -a     
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N] y
Deleted Images:
untagged: ubuntu:latest
untagged: ubuntu@sha256:c303f19cfe9ee92badbbbd7567bc1ca47789f79303ddcef56f77687d4744cd7a
deleted: sha256:6348795f7982209fa3f3c665a93a33a9445335f33bd630074733b84f71e4ad05
deleted: sha256:2b0e983ecc99242860e218f998b4279f437a309b47fb365e62bc730bf9464d54
deleted: sha256:738661d26eb3670844042d5cda8b456a7dae29adf3a1408b9782a2b1927dc491
deleted: sha256:75b4486601a652044de9e48de63306ebebfa7cb7e56a4a182aa52de2cfd98048
deleted: sha256:d7e2a043ce86bd96bd10f3171bcb089bad4503d7aa60f182d54307c4932b911f

Total reclaimed space: 46.74MB

This will help keep our host system as clean and clutter free as possible.

In Part 3 of this series, I'll cover Docker Compose.

Continue to Part 3.

If you found this helpful, please let me know below in the comment below or Tweet at me @eiddor!  I'd also like to hear any other interesting things you discovered while getting started with Docker.