Fun with Docker - Part 2b: More Getting Started
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:
- Part 1 - Docker - An introduction
- Part 2a - Getting started with Docker
- Part 2b - More getting started with Docker (this post)
- Part 3 - Docker Compose
- Part 4 - Docker Volumes (and Bind Mounts)
- Part 5 - Docker Networking
- Part 6 - Monitoring your Docker setup with Portainer
- Part 7 - Setting up this blog with Let's Encrypt, Nginx, and Ghost
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 backgroundcentos
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.
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.