Docker · lesson 1 of 9
Intermediate
3Also part of Docker
5- + Deploying FastAPI with Docker and Kubernetes: Images, Signals, and Probes from FastAPI
- + Dockerizing Spring Boot Applications from Java
- + Bash Process Management from Bash
- + CIS Docker Benchmark Checks with docker-bench-security from Cloud Security
- + Signing Container Images with Cosign: Verify the Digest, Not the Tag from DevSecOps (AppSec in the pipeline)
Introduction to Docker
Run your first Docker containers and see what they are: ordinary Linux processes sharing the host kernel, started from an image in well under a second.
By SkillByExample · Updated
Docker packages an application and everything it needs into an image, and runs that image as an isolated container. Every example below was run against Docker Engine 29 on Linux; on macOS or Windows, install Docker Desktop and the commands are identical.
Check the client and the daemon
The docker command is a client that talks to a background daemon (dockerd), which does the
real work. docker version shows both halves.
docker version --format 'client {{.Client.Version}}, server {{.Server.Version}} ({{.Server.Os}}/{{.Server.Arch}})'
$ docker version --format 'client {{.Client.Version}}, server {{.Server.Version}} ({{.Server.Os}}/{{.Server.Arch}})'
client 29.8.2, server 29.8.2 (linux/amd64)
If the server half is missing and you see Cannot connect to the Docker daemon, the client is
installed but the daemon is not running — start Docker Desktop, or sudo systemctl start docker on
Linux.
Your first container
docker run --rm hello-world:linux
$ docker run --rm hello-world:linux
Unable to find image 'hello-world:linux' locally
linux: Pulling from library/hello-world
4f55086f7dd0: Pulling fs layer
d5e71e642bf5: Download complete
4f55086f7dd0: Download complete
4f55086f7dd0: Pull complete
Digest: sha256:5e22040d441e5fb3aed38368acbe8486b575d7018df38dbdfbc7311fbb2ef3a9
Status: Downloaded newer image for hello-world:linux
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
docker run did three things: it found no local image, pulled it from Docker Hub, then
created and started a container from it. The digest pins the exact image content; the tag
(linux) is just a movable label. --rm deletes the container once it exits. Run the command
again and the pull lines disappear, because the image is now cached.
A container shares the host kernel
uname -r
docker run --rm alpine:3.20 sh -c 'uname -r; grep PRETTY /etc/os-release'
$ uname -r
6.18.44-fc-v77
$ docker run --rm alpine:3.20 sh -c 'uname -r; grep PRETTY /etc/os-release'
Unable to find image 'alpine:3.20' locally
3.20: Pulling from library/alpine
25f1d6b1951a: Pulling fs layer
...
Status: Downloaded newer image for alpine:3.20
6.18.44-fc-v77
PRETTY_NAME="Alpine Linux v3.20"
The container reports Alpine Linux as its operating system but the same kernel version as the host. That is the difference from a virtual machine: the image supplies the userland (files, libraries, shell), and the host supplies the kernel. Nothing boots, so there is no OS start-up cost.
A container is just a process
docker run -d --name sleeper alpine:3.20 sleep 300
docker top sleeper
ps -eo pid,comm | grep -w sleep
docker exec sleeper ps
docker rm -f sleeper
$ docker run -d --name sleeper alpine:3.20 sleep 300
016183fe1a4279219b0a07b3d75d90750f19900d8bfcf3bc89e7cfd0042fb905
$ docker top sleeper
UID PID PPID C STIME TTY TIME CMD
root 1933 1908 20 23:32 ? 00:00:00 sleep 300
$ ps -eo pid,comm | grep -w sleep
1933 sleep
$ docker exec sleeper ps
PID USER TIME COMMAND
1 root 0:00 sleep 300
7 root 0:00 ps
$ docker rm -f sleeper
sleeper
From the host, the container’s sleep is plain PID 1933 in the normal process list. Inside the
container, the same process is PID 1, and it can only see its own processes. That narrowed view
is a Linux PID namespace; Docker adds similar namespaces for the network, mounts and hostname,
plus cgroups to cap CPU and memory. Isolation is a set of kernel features applied to a process,
not a separate machine.
Containers start fast
time docker run --rm alpine:3.20 echo hi
$ time docker run --rm alpine:3.20 echo hi
hi
real 0m0.285s
user 0m0.000s
sys 0m0.039s
Creating a container, running a command in it and removing it took under a third of a second, most of it client/daemon round trips. A VM boot is measured in tens of seconds. This is why containers are used for throwaway jobs like CI steps and one-off tools.
Images versus containers
docker image ls --format '{{.Repository}}:{{.Tag}} {{.Size}}'
$ docker image ls --format '{{.Repository}}:{{.Tag}} {{.Size}}'
alpine:3.20 12.2MB
hello-world:linux 25.9kB
An image is a read-only template — a stack of filesystem layers plus metadata like the default
command. A container is an instance of an image with a thin writable layer on top. Every
docker run above created a new container from the same cached alpine:3.20 image, which is only
12 MB on disk. A Dockerfile is the recipe that builds an image, and a registry (Docker Hub
by default) is where images are pushed and pulled. The next lessons cover each in turn.
Practice
- Print the exact Alpine release inside the
alpine:3.20image (it is stored in/etc/alpine-release).
Solution
docker run --rm alpine:3.20 cat /etc/alpine-release
$ docker run --rm alpine:3.20 cat /etc/alpine-release
3.20.10
The tag 3.20 is a moving label for the latest 3.20.x patch release.
- Start a detached container named
napperthat sleeps for 60 seconds, and print its host PID and status withdocker inspect. Remove it afterwards.
Solution
docker run -d --name napper alpine:3.20 sleep 60
docker inspect --format '{{.State.Pid}} {{.State.Status}}' napper
docker rm -f napper
$ docker run -d --name napper alpine:3.20 sleep 60
a53b9d8cc41e5ddf58538976f3cf58c5751be1309052b5cf1f655b03f0bc4dec
$ docker inspect --format '{{.State.Pid}} {{.State.Status}}' napper
1626 running
$ docker rm -f napper
napper
-d prints the full container ID and returns immediately. The PID is the process’s ID on the host.
- Run
uname -rin adebian:12-slimcontainer. Is the kernel version different from the one Alpine reported?
Solution
docker run --rm debian:12-slim uname -r
$ docker run --rm debian:12-slim uname -r
Unable to find image 'debian:12-slim' locally
12-slim: Pulling from library/debian
...
Status: Downloaded newer image for debian:12-slim
6.18.44-fc-v77
Same kernel. Debian and Alpine supply different userlands; every container on the host shares one kernel.
- Run
alpine:3.20 echo donewithout--rmand a--nameofonce. Show that the stopped container still exists, then remove it.
Solution
docker run --name once alpine:3.20 echo done
docker ps -a --filter name=once --format '{{.Names}} {{.Status}}'
docker rm once
$ docker run --name once alpine:3.20 echo done
done
$ docker ps -a --filter name=once --format '{{.Names}} {{.Status}}'
once Exited (0) Less than a second ago
$ docker rm once
once
A container that exits is stopped, not deleted. Without --rm, stopped containers accumulate until
you remove them.