Introduction
A container is a run-time instance of an image, and multiple containers can be started from the same image. While the shared image is read-only, each container gets its own writable layer, making containers fast, lightweight, and disposable compared to traditional virtual machines.
Containers vs Virtual Machines
Containers and virtual machines (VMs) both virtualize resources to run applications, but in fundamentally different ways. VMs virtualize hardware: a hypervisor claims physical resources like CPU and RAM and carves them into virtual machines that each require their own full operating system. Containers, on the other hand, virtualize the operating system itself, sharing the host's kernel while packaging application code into isolated, lightweight units.
Because every VM needs its own OS, VMs consume more resources and take longer to boot. Containers avoid this overhead entirely, which means:
- Containers are smaller and more portable
- More containers can run on the same infrastructure compared to VMs
- Containers start significantly faster
- Fewer operating systems need to be patched and managed
- Containers present a smaller attack surface
One historical concern with containers is the shared-kernel model, since a kernel vulnerability could theoretically affect every container on a host. Modern container platforms mitigate this with security technologies such as SELinux, AppArmor, seccomp, and image vulnerability scanning.
Images and Containers
When multiple containers are started from a single image, Docker gives each container its own thin read-write (R/W) layer on top of the shared, read-only image. Changes made inside a container are written to this layer. Stopping a container preserves this layer, but deleting a container removes it permanently.
Starting a Container
The docker run command is the most common way to start a new container:
docker run -d --name webserver -p 5005:8080 nigelpoulton/ddd-book:web0.1Here, -d runs the container in the background, --name assigns it a name, and -p maps a host port to a container port. If the image isn't available locally, Docker automatically pulls it from Docker Hub before starting the container.
How Containers Start Applications
Docker determines what to run inside a container in one of three ways: an Entrypoint instruction baked into the image, a Cmd instruction in the image, or a command passed directly on the CLI. Entrypoint instructions cannot be overridden from the command line, while Cmd instructions can.
Connecting to a Running Container
The docker exec command allows commands to be run inside a running container, either interactively or as a one-off remote execution:
docker exec -it webserver shSince most containers are stripped down to essential tools, many familiar commands may not be available inside them.
Container Processes and Inspection
Most containers run a single main process, always assigned PID 1. If this process is killed, the entire container stops, since containers only exist while their main process is running. The docker inspect command provides detailed configuration and runtime information about a container, including its state, port bindings, and entrypoint.
Persisting and Losing Changes
Changes made to a running container's filesystem are stored in its writable layer and survive stop and restart operations. However, once a container is deleted with docker rm, those changes are permanently lost, and starting a new container from the same image won't include them. Modifying live containers directly is generally considered an anti-pattern; the recommended approach is to build a new image with the required changes and replace the container.
Debugging Slim Containers with Docker Debug
Docker Debug (available with a Pro, Team, or Business subscription) solves the common problem of debugging minimal images that lack a shell or troubleshooting tools. It attaches a temporary toolbox to a container or image, allowing tools like ping, vim, or nslookup to be used even if they aren't installed. Changes made while debugging a running container persist, but changes made while debugging a stopped container or an image are discarded once the session ends.
Self-Healing with Restart Policies
Docker supports four restart policies that control how containers recover from failure:
no(the default) — never automatically restartson-failure— restarts only on a non-zero exit codealways— always restarts, including after a daemon restartunless-stopped— always restarts, unless manually stopped
A restart policy is applied when starting a container, for example:
docker run --name neversaydie -it --restart always alpine shConclusion
Containers offer a fundamentally more efficient way of virtualizing applications compared to VMs, trading dedicated operating systems for shared-kernel isolation. Understanding how containers start, persist changes, can be debugged, and recover from failure through restart policies is essential for working with them reliably in real-world environments.