Containers, namespaces and cgroups
A container is processes with a restricted view, not a virtual machine — how namespaces isolate each resource, what cgroups limit, and how layered images work.
One namespace per kind of isolation
| Namespace | Isolates | Visible effect |
|---|---|---|
| PID | Process ids | The container sees its own PID 1 |
| Mount | File system tree | Its own root and mounts |
| Network | Interfaces, routes, ports | Its own IP, its own port space |
| UTS | Hostname and domain name | Its own hostname |
| IPC | Shared memory and message queues | Isolated queues |
| User | User and group id mapping | Root inside is unprivileged outside |
| Cgroup | Cgroup hierarchy view | Only its own slice is visible |
| Time | Boot and monotonic offsets | Boot time differs from the host |
# the same view a container runtime builds, done by hand
unshare --pid --fork --mount-proc --uts --ipc --net bash
# inside: a new PID 1, a new hostname, no host network interfaces yet
# what the current process can see
ls -l /proc/self/ns/
readlink /proc/self/ns/net
lsns | head- Namespaces change what a process can see; they do not limit how much it can consume.
- The user namespace is what makes rootless containers possible, mapping container root to an unprivileged host user.
- A container shares the host kernel, so a kernel vulnerability is a shared risk in a way that does not apply to a virtual machine.
- Mounting the host's namespace is how debugging tools inspect a running container from outside.
cgroups constrain resources
# inspect the cgroup of a process and its limits
cat /proc/self/cgroup
systemd-cgtop
# a slice with CPU and memory limits
systemctl set-property user-1000.slice MemoryMax=4G CPUQuota=200%
# the kernel's own view
cat /sys/fs/cgroup/system.slice/api.service/memory.max
cat /sys/fs/cgroup/system.slice/api.service/cpu.max
cat /sys/fs/cgroup/system.slice/api.service/memory.current
cat /sys/fs/cgroup/system.slice/api.service/memory.events| Controller | Limits | Failure mode when exceeded |
|---|---|---|
| memory | Hard and soft limits, swap use | The OOM killer kills the largest process in the cgroup |
| cpu | Quota per period, weight | Throttling; the process is paused until the next period |
| io | Bandwidth and IOPS weights | Slower I/O, no error |
| pids | Maximum process count | Fork fails with a resource error |
| cpuset | Which cores may be used | Scheduling only on the allowed cores |
| hugetlb | Huge page reservation | Allocation fails |
A memory limit triggers an OOM kill of a process inside the cgroup, whereas an unbounded container can trigger a host-level kill instead. Setting the limit is what makes the failure local and diagnosable.
Images and why they are layered
- An image is a stack of read-only layers; a container adds one writable layer on top.
- A copy-on-write file system means a small change copies one file, not the whole layer.
- Reusing a base layer shares its bytes across every image built on it, which is why registry pulls are usually fast.
- Layer order matters for cache hits: put rarely changing instructions early and frequently changing ones last.
- The writable layer disappears with the container, which is why persistent data must be in a volume or a bind mount.
# dependencies first, source last: this keeps the expensive layer cached
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build
# a non-root runtime user costs nothing and removes a whole class of risk
USER node
ENTRYPOINT ["node", "dist/server.js"]⚠️
--memory without --memory-swap can still let a container swap heavily and appear slow rather than fail. Size the limit against the working set, watch memory.events for OOM counts, and treat a rising OOM rate as a capacity signal rather than noise.FAQ
Is a container as isolated as a virtual machine?
No. Containers share the host kernel, so kernel bugs and some side channels cross the boundary. For untrusted workloads, a VM or a sandboxed runtime is the stronger boundary.
Why does my container see the host's CPU count?
CPU count is not namespaced. Runtime-aware libraries must read the cgroup quota, and many languages needed updates to do that — check the version before trusting thread pool sizing.
Related
Virtualisation and hypervisors Storage and file systems
Last refreshed 2026-09-18.