A Practical Introduction to Containers with Docker
What is a Container?
A container is an isolated Linux process running on a Linux-based operating system. The keyword here is isolated. A container has its own hostname, root filesystem, process IDs, mount points, and user IDs independent of the host OS. Even though a container is only a Linux process, this isolation of attributes from the host OS makes a container appear as a separate operating system with its own files, users, and network interfaces.
Containers vs Virtual Machines
If a container sounds awfully similar to a virtual machine, that is because it is. The difference is that a virtual machine simulates the hardware of a physical machine (including its motherboard, CPU, RAM, and NIC), whereas a container only simulates the user space of an operating system.
What is this user space? An operating system consists of two parts: kernel and user space. The kernel runs with higher privileges and conducts core operating system tasks like memory management, process creation, and block I/O management. The user space consists of every other program in the operating system that is not the kernel. This includes applications like bash, ip, or even Chrome. A container only simulates user space and not the kernel because, as a Linux process, it can simply use the Linux kernel of the host OS.
So a host OS can have multiple containers, but they will all share the host OS kernel. This makes containers blazing fast to deploy, because you’re skipping the overhead of simulating the kernel (or hardware—as in the case with virtual machines).
Docker
One way to run containers is with a program called Docker. Docker is used by developers for creating local dev environments to deploy their applications inside containers. Homelab hobbyists use Docker to deploy popular web apps like Pi-Hole and Jellyfin.
For a practical understanding of containers, you will need to install Docker to follow the rest of the article:
- Install Docker Engine on Linux and follow post-installation steps
- Install Docker Desktop on Mac (uses Linux VM)
- Install Docker Desktop on Windows (uses WSL)
Demo 1 – Print OS Info
Now open a terminal and spin up a container that mimick’s Ubuntu’s user space.
| |
This command deploys an Ubuntu container and runs the command cat /etc/os-release inside it. Inside the container, os-release shows the OS as Ubuntu regardless of what the host OS is. Immediately when this command stops running, the container stops.
You can check this yourself:
| |
This will print something like the following:
| |
It shows the ID of the container you ran, what command you specified, and when the container exited. There is also a randomly generated name for the container you can use for identification in future Docker commands. In this case, the name is agitated_ganguly.
Demo 2 – Interactive Shell
But this container exited the moment the command stopped running. To prevent the container from exiting, let’s run a command like bash that keeps running until you explicitly close it.
| |
The
-tand-ioptions are necessary here since we want bash to run as an interactive terminal.
Voila, now we’re inside the container. Notice how the hostname of the container is different from the host OS according to the bash prompt. You can use ls and cd to roam around inside the container and run exit to get out of it. The container will stop the moment you exit bash, i.e. when the command specified with docker run stops.
Demo 3 – Test Web App Deployment
But you don’t always need to specify a command when running Docker containers.
Here’s an example: https://github.com/docker/welcome-to-docker
The repo contains code for a web app. However, there is also code for building a Docker container in Dockerfile.
Important things to note in this file:
- The container is built on top of an existing container image called
node-21:alpine. This is an Alpine Linux container with NodeJS installed. - In the last line, a
docker runcommand is specified withCMD. The command runs the HTTP server. This means we won’t need to specify a command when we rundocker runfor this container.
Let us deploy this container without a command to see it in action:
| |
-p 8080:80 tells Docker to map the container’s port 80 to the host’s port 8080. This allows you to access the container’s port 80, which the HTTP server inside the container is listening on, by visiting http://localhost:8080 from the host. Try it with a web browser or curl.
If you’re running Docker on a remote computer, you will need to replace
localhostwith the domain name or IP address of this host.
Once done, you can stop the container by sending SIGINT (Ctrl + C).
Demo 4 – Deploying Joplin with Docker Compose
Finally, let’s deploy an actually useful app. But instead of deploying with docker run, use docker compose. This approach is more popular in production and among homelabbers.
We will deploy Joplin, an open-source note-taking application. First, take a look at the docker run command here. The multiline Docker CLI command is not pleasant to read or understand.
So instead we will make a Compose file with the equivalent YAML configuration here. First, make a directory named joplin. Then create a file inside named compose.yaml with the Compose configuration. Replace /path/to/config with ./config for the sake of the demo. You would normally point this to the path in your host OS where you want Joplin’s persistent configurations to be saved.
Now with joplin as your current working directory, run
| |
Option
-druns the container in background, i.e. in detached mode. This option exists fordocker runas well.
This will deploy the container (to be precise, the “service”) defined in the Compose file.
Visit https://localhost:3001 and voila, you have your own note-taking application accessible from a browser!
If ever needed, you can gracefully undeploy the container by running
| |
from the joplin directory where compose.yaml resides.
This will not delete the ./config directory where Joplin stores the notes you create. So if you run docker compose up -d again to deploy a new Joplin container, your notes will still be there. That is the magic of containers!
Wrapping Up
You’ve learned what containers are and how to use Docker to spin up containers. Now it’s your turn to deploy an app you want use using Docker! Check out this awesome list and deploy an app you like. Whatever app you choose, chances are it supports Docker-based deployment.
More Resources
- Komodo: a web UI for Docker to make life easier.
- Kubernetes documentary by Honeypot: learn about the most popular container orchestrator used in production deployments.
- Linux Container Primitives: cgroups, namespaces, and more! by Samuel Karp: learn the internal mechanics of Linux containers.
- Containers from Scratch by Liz Rice: a demo creating Linux containers from scratch with Go!
- LXC: another type of containers used often like VMs unlike Docker.