Cloud Native 5 Minutes at a Time: Creating, Observing, and Deleting Containers
One of the biggest challenges for implementing cloud native technologies is learning the fundamentals — especially when you need to fit your learning in a busy schedule.
In this series, we’ll break down core cloud native concepts, challenges, and best practices into short, manageable exercises and explainers, so you can learn five minutes at a time. These lessons assume a basic familiarity with the Linux command line and a Unix-like operating system — beyond that, you don’t need any special preparation to get started.
Table of Contents
- What is a Container?
- Creating, Observing, and Deleting Containers←You are here
- Build Image from Dockerfile
- Using an Image Registry
- Volumes and Persistent Storage
- Container Networking and Opening Container Ports
- Running a Containerized App
- Multi-Container Apps on User-Defined Networks
In the last lesson, we learned how containers work and installed a container engine. Now that we have Docker installed, let’s create a container. In your terminal, enter:
docker container run alpine echo Hello World
This command will call up Docker Engine and ask it to create a new container with container run
.
We’re telling it to use a container image (more about those in our next lesson) called alpine
which gives our container the binaries and libraries of a very lightweight Linux distribution as its foundation.
The next parameters specify a process to run within our new container: the system command echo
which outputs strings. “Hello World”
is an argument passed to echo
— the string we would like to output.
When we run this command, Docker should download the alpine image and then run the process, producing terminal output like this:
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
59bf1c3509f3: Already exists
Digest: sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300
Status: Downloaded newer image for alpine:latest
Hello World
Those using Docker Desktop may open the application and find the new container represented (with a randomly generated name) in the graphical user interface.
Now let’s get a little more advanced and create a container with a continuously running process. We’ll use the same basic command structure as before, but this time we’ll run the ping command inside our container:
docker container run alpine ping localhost
We’ll notice a few differences this time. First, we already had the alpine
image on our machine, so the container started immediately. Second, the command doesn’t finish the way echo
did. The ping process is ongoing, continuously checking network connectivity. We can stop the process by pressing CTRL+C.
Observing containers
After stopping the process, let’s take a look at the containers on our system using the terminal. We can already see this in Docker Desktop if we’re on Mac or Windows, but it’s useful to be able to bring up this information quickly in the terminal.
docker container ls -a
Since we used the -a
flag, this will list all of our containers, whether they are running or not — we should see both our echo
and our ping
containers, along with some useful information such as their names and container IDs. Make a note of the ping
container’s ID. Mine, for example, is 5480aa85d1c5
.
Now let’s restart our ping container. Using the start command will run the process separately from our current shell, so we can enter other commands while the container process runs. You’ll replace 5480aa85d1c5
below with the ID of your container.
docker container start 5480aa85d1c5
The terminal will return the container ID to let us know that it’s running. We can also use the ls command without the -a
flag:
docker container ls
This shows us only running containers. Our ping
container should be listed here, but not the echo
container.
Before we finish up, let’s inspect our container a little more closely in order to better understand what’s happening under the hood. If we use the top command with a running container ID, we’ll get information about the processes running within the container from the perspective of the host system:
docker container top 5480aa85d1c5
This gives us output looking something like this:
UID PID PPID C STIME TTY TIME CMD
root 3509 3481 0 16:17 ? 00:00:00 ping localhost
The second column, PID, indicates the process identifier number for our containerized ping. This is the number the host machine’s operating system kernel is using to keep track of processes running on the machine, and in my case we can see that it is one of thousands. So we see that the ping process is using the host OS kernel, and we see how the kernel perceives the process: number 3509 in a long list.
But we said before that one way containers isolate themselves is through kernel namespaces: the way processes signify themselves relative to the kernel within the container. Metaphorically, we can think of this as the way the process sees itself versus the way the wider world sees it. Let’s take a look at the container from its own perspective:
docker container exec 5480aa85d1c5 ps
This gives us information on the processes running within the container from its perspective. In the container’s kernel namespaces, the ping process is PID 1. We’ll also see a listing for the ps
command we just ran within the container. And that’s it!
The ping running as PID 1 within the container is the very same process as the one signified by PID 3509 on the wider system, but because the container has its own isolated kernel namespaces, its system doesn’t see the wider world outside.
Removing containers
Now let’s clean up. We can stop our running container (again, replacing the numeric ID with your own) using:
docker container stop 5480aa85d1c5
Now we can clean up using:
docker container rm 5480aa85d1c5
It’s an important piece of system hygiene to remove unused containers. Make sure you remember to remove the echo container as well — you can use the ls
command with the -a
flag to retrieve its id.
In the next lesson, we’ll take a closer look at container images.