DEV Community

Backend By Dmytro
Backend By Dmytro

Posted on

Docker does not exist: processes and reliability

Stop me if this has happened to you: you restart the Docker service expecting your Nginx container to die, and it just... keeps serving traffic. That's not a bug. It's the architecture telling you something important about what a "container" actually is.

Here's the mental model shift: a Docker container isn't a VM-like object managed by Docker. It's an application process running on the host kernel, supervised by a chain of other processes—dockerd, containerd, containerd-shim, and runc. Docker as the brand is real; Docker the monolithic runtime is not.


The Setup

Everything below runs on Fedora Linux. MacOS/Windows users: Docker runs a Linux VM under the hood, so the process tree will be inside that VM, not directly on your host.

First, enable live-restore in /etc/docker/daemon.json— this is what lets containers survive a Docker Engine restart:

{
    "live-restore": true
}
Enter fullscreen mode Exit fullscreen mode

Then restart Docker and spin up Nginx:

sudo service docker restart
docker run --rm --name=my-nginx -p 8080:80 -d nginx
Enter fullscreen mode Exit fullscreen mode

What the Host Actually Sees

ps -ef --forest | grep -E 'docker|containerd|nginx' | grep -v grep
Enter fullscreen mode Exit fullscreen mode

The output isn't a single "container" process. You'll see a tree: dockerdcontainerdcontainerd-shimnginx. Four separate processes on the host, each with a distinct job. That tree structure is the whole story.

dockerd: REST API and Manager, Not a Runtime

When you run docker run ..., the CLI translates it into a REST API request sent to dockerd. That's it — the CLI is a thin HTTP client.

dockerd handles image management, volume management, and port-mapping (via a spawned docker-proxy process). What it doesn't do is actually run your container. For that, it makes gRPC calls to containerd over /run/containerd/containerd.sock. Think of dockerd as the control plane; the execution happens elsewhere.

containerd: The Actual Runtime

containerd is where execution begins. It's OCI-compliant, which is why Kubernetes can use it directly without Docker at all.

For each container start, containerd:

  • pulls the image from the registry,
  • prepares the root filesystem,
  • spawns a containerd-shim process.

The shim is the key piece. That's what makes the restart behavior make sense.

containerd-shim: The Per-Container Supervisor

Each container gets exactly one containerd-shim. Its responsibilities:

  • Runs runc to set up Linux namespaces and cgroups, then runc exits—it's just a setup tool
  • Holds the stdin/stdout/stderr file descriptors for the container process (that's how docker logs and docker attach work)
  • Relays signals like SIGTERM to the application
  • Acts as the direct parent of your app process—so the app stays alive even if containerd or dockerd goes away

That last point is the one most people don't expect.

Proof: Stop Docker, Container Keeps Running

sudo systemctl stop docker
ps -ef --forest | grep -E 'docker|containerd|nginx' | grep -v grep
Enter fullscreen mode Exit fullscreen mode

dockerd is gone. containerd, containerd-shim, and nginx are still there. With live-restore: true, the shim holds the process alive independently of the engine.

Go further—kill containerd itself:

sudo kill $(pgrep -o containerd)
ps -ef --forest | grep -E 'containerd|nginx' | grep -v grep
Enter fullscreen mode Exit fullscreen mode

The shim and Nginx survive. The shim is the direct parent; containerd dying is irrelevant to the running workload.

This is what makes zero-downtime Docker Engine upgrades possible: live-restore + the shim architecture means your app doesn't care what the daemons are doing.

Filesystem: No Hard Boundary Against Host Root

Containers provide process isolation—not a security boundary against a root user on the host. Here's proof:

NGINX_PID=$(pgrep -o nginx)
sudo cat /proc/$NGINX_PID/root/etc/nginx/nginx.conf
Enter fullscreen mode Exit fullscreen mode

That's the container's Nginx config, read directly from the host via /proc. No volume mounts, no docker exec. The container's filesystem is just a view the kernel provides; /proc/<pid>/root exposes it to any sufficiently privileged host process.

Security implication: if the host is compromised at root level, assume all containers on that host are too.

What This Changes Operationally

A few places where this model pays off immediately:

  • Debugging "container still running after Docker restart": check for containerd-shim and your app process on the host—they're just ps entries
  • Port-mapping issues: docker-proxy is a real process; it shows up in ss or lsof output
  • Engine upgrades: live-restore: true + a shim-aware mental model = no forced downtime
  • Security reviews: treat host root access as full container access, because /proc makes it so

The next time you type docker run, what's actually happening is: the CLI talks to an API, the API tells a runtime, the runtime spawns a supervisor, the supervisor sets up isolation via runc and then stays out of the way. Your Nginx is just a process. And that's exactly what makes containers fast, composable, and debuggable with standard Linux tools.

Watch the full video walkthrough (diagrams + demo)

Top comments (0)