# What a Container Actually Is: Namespaces and cgroups

> A container isn't a lightweight VM — there's no guest kernel, no virtual hardware. It's just a normal Linux process wearing two kernel features as a disguise. Strip away Docker and the magic is surprisingly small.

URL: https://harshith.in/blog/what-is-a-container/
Author: Harshith Sunku (Staff Engineer) — https://harshith.in/about/
Tags: containers, linux, systems

People picture a container as a tiny virtual machine. It isn't. There's no guest
OS, no hypervisor, no emulated hardware. A container is an ordinary process on the
host kernel that has been lied to about what it can see and limited in what it can
use. Two kernel mechanisms do all the work: **namespaces** and **cgroups**.

## A Container Is Just a Process

Run a container and look on the host — you'll find its processes in the normal
process list, scheduled by the normal scheduler, sharing the host kernel:

```text
   VM:                          Container:
   ┌─────────────┐              ┌─────────────┐
   │ guest kernel│              │   process   │
   ├─────────────┤              └──────┬──────┘
   │ hypervisor  │                     │ syscalls
   ├─────────────┤              ┌──────▼──────┐
   │ host kernel │              │ host kernel │  ← shared, no guest
   └─────────────┘              └─────────────┘
```

That shared kernel is why containers start in milliseconds and cost almost
nothing — and also why isolation is weaker than a VM's.

## Namespaces: Lying About What Exists

A **namespace** gives a process its own private view of one kind of global
resource. The kernel has several:

```text
   pid     → its own process tree; PID 1 inside is some PID on the host
   mnt     → its own filesystem mounts (the "container's filesystem")
   net     → its own interfaces, routes, ports
   uts     → its own hostname
   ipc     → its own shared memory / semaphores
   user    → its own UID range; root inside ≠ root on host
```

Put a process in fresh namespaces and it sees a pristine system: PID 1, its own
network stack, its own root filesystem. It *believes* it's alone on the machine.
Nothing is virtualized — the kernel just filters what the process can perceive.

## cgroups: Capping What It Uses

Namespaces control visibility; **control groups (cgroups)** control consumption.
They cap and account for CPU, memory, I/O, and process count:

```text
   cgroup: web-app
     memory.max   = 512M     → OOM-killed if exceeded
     cpu.max      = 50%      → throttled past its share
     pids.max     = 100
```

This is what stops one container from eating the whole host. `docker run -m 512m
--cpus 0.5` is just writing these cgroup limits for you.

## Docker Is the Convenience Layer

Strip it back and a container is: a process started in new namespaces, confined by
cgroups, with its root filesystem swapped (`pivot_root`) to an unpacked image.
You can build one by hand with `unshare` and `cgcreate`. Docker/containerd add
the image format, layered filesystem, networking setup, and registry — enormous
convenience, but not the isolation itself.

```bash
# A crude container by hand: new namespaces + a different root
sudo unshare --pid --net --mount --uts --fork --mount-proc \
    chroot /path/to/rootfs /bin/sh
```

## Why It Matters

Because the kernel is shared, a kernel exploit from inside a container can reach
the host — unlike a VM, where you'd also have to break the hypervisor. That's the
core security tradeoff, and the reason sandboxes like gVisor and Kata exist to put
some of the VM boundary back.

## Takeaways

- A container is a host process, not a VM — same kernel, no guest OS, hence the
  millisecond startup.
- Namespaces give it a private view of PIDs, mounts, network, etc.; it only
  *thinks* it's alone.
- cgroups cap its CPU, memory, and I/O so it can't starve the host.
- Shared kernel means weaker isolation than a VM — the tradeoff behind container
  security tooling.
