What a container actually is

Docker · lesson 27 of 31 · 6 min read

No such kernel object exists - it is namespaces plus cgroups plus a filesystem.

Open this lesson in the learning hub

Key points

  • There is no container primitive in the Linux kernel. A container is an ordinary process with restricted visibility and restricted resources, assembled from features that predate Docker.
  • Namespaces control what a process can see. PID gives it its own process tree so it believes it is PID 1; NET gives it its own interfaces and routing table; MNT its own filesystem view; UTS its own hostname; IPC, USER and CGROUP the rest.
  • cgroups control what it can consume - CPU, memory, block I/O, process count. Namespaces are about isolation, cgroups about limits, and confusing the two explains many wrong mental models.
  • Because it is just a process, a container has no boot, no kernel of its own, and shares the host kernel. That is why a container starts in milliseconds and why a kernel exploit escapes it.
  • PID 1 has special semantics: it does not get default signal handlers, and it must reap orphaned children. A JVM as PID 1 that ignores SIGTERM will be SIGKILLed, which is the usual cause of ungraceful shutdown.
  • The USER namespace is what makes rootless containers possible: root inside can map to an unprivileged uid outside, so a container escape does not hand over the host.

Example

# A container is a process. Look at it from the host:
$ docker run -d --name app nginx
$ docker inspect -f '{{.State.Pid}}' app
#   48213

$ ps -o pid,ppid,cmd -p 48213      # an ordinary process on the host
$ ls -l /proc/48213/ns/            # ... with its own namespaces
#   ipc -> ipc:[4026532other]
#   mnt -> mnt:[4026532...]
#   net -> net:[4026532...]
#   pid -> pid:[4026532...]

# Inside, it believes it is PID 1:
$ docker exec app ps aux
#   PID   USER   COMMAND
#     1   root   nginx: master process

# The limits, in the cgroup:
$ cat /sys/fs/cgroup/system.slice/docker-<id>.scope/memory.max
$ cat /sys/fs/cgroup/system.slice/docker-<id>.scope/cpu.max
#   50000 100000   -> 0.5 CPU: 50ms of every 100ms period

---
# THE PID 1 PROBLEM. Shell form makes the shell PID 1, not the JVM:
#
#   CMD java -jar app.jar        -> /bin/sh -c "java -jar app.jar"
#                                   sh is PID 1 and does NOT forward SIGTERM
#                                   -> no graceful shutdown, SIGKILL at timeout
#
#   CMD ["java", "-jar", "app.jar"]   -> java IS PID 1, receives SIGTERM

# Exec form, plus an init to reap zombies if the app spawns children:
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
#   docker run --init ...        # or tini as PID 1

A container is a process with namespaces and cgroups - so it shares the host kernel, and its PID 1 must handle SIGTERM itself.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Docker course, and every lesson in it is listed on the Docker contents page.