Side reading

"It runs in a container" is used as though it settled a security question. It changes the question rather than settling it.

What actually separates a container from your machine #

Two kernel features, and a handful of smaller ones.

Namespaces decide what a process can see. There are several, each covering a different kind of thing:

Namespace What it hides
mount the filesystem. The container sees its image, not your disk
pid other processes. Inside, the container's own process is number 1
net interfaces, addresses, ports, routing
uts the hostname
ipc shared memory and message queues
user maps user ids inside to different ones outside

Cgroups account for resources and enforce the limits you configure: memory, CPU, block-device throughput and process count. Defaults are not automatically safe; mem_limit and pids_limit matter because they turn accounting into an availability boundary.

On top of those sit capabilities (splitting root's powers into pieces that can be dropped individually), seccomp (restricting which system calls a process may make at all) and AppArmor or SELinux (mandatory access control policies). Docker applies default profiles for all three, and they are a large part of why the default is not as weak as it might be.

The part people get wrong #

There is one kernel.

When a container makes a system call, it makes it to the same kernel your SSH session is talking to. Namespaces limit what that call can refer to. They do not put a second kernel in the way.

So a kernel vulnerability that allows privilege escalation is potentially a container escape. A virtual machine, running its own kernel on emulated hardware, does not have that property: an attacker would need to break the guest kernel and then the hypervisor.

Practically, on a home server, that means:

  • Patch the host. This is the single most important thing, and it is why chapter 3 put upgrades on a timer.
  • Do not run images you have no reason to trust. A container from a random registry is code running on your machine with a thinner boundary than you imagine.
  • "It's only a container" buys you slightly less worry, not none.

Things that make the boundary much thinner #

Several common flags hand back most of the isolation. Recognise them in a compose file you found on the internet:

--privileged turns nearly all of it off: capabilities, device restrictions, and most of the profiles. Assume a privileged container is equivalent to root on the host. Very little genuinely needs it.

Mounting the Docker socket (-v /var/run/docker.sock:/var/run/docker.sock) gives the container the ability to start other containers, including a privileged one that mounts your root filesystem. Equivalent to root, one step removed. Plenty of popular management tools ask for exactly this.

--net=host removes the network namespace. The container binds ports directly on your host, which also means your firewall rules apply normally again, which is occasionally the reason people do it.

--pid=host lets the container see and signal every process on the machine.

Bind-mounting sensitive paths. -v /:/host is the obvious one. -v /etc:/etc:ro is subtler and still tells an attacker a great deal.

Running as root inside with no user namespace remapping. Root in the container is root on the host for anything it can reach through a bind mount. If a container writes to a mounted directory as uid 0, those files are owned by real root.

What to do about it on a home server #

You are not defending against a nation state. You are defending against automated exploitation of a known vulnerability in something you exposed and forgot to update. Ranked by how much they actually help:

  1. Keep the host patched, automatically, with reboots you actually perform.
  2. Run fewer things, and fewer things reachable from outside.
  3. Pin images to digests and review before bumping, so a compromised publisher cannot push new code onto your machine silently.
  4. Set measured resource limits. Availability is a security property. Leave headroom and verify that normal upgrades and background jobs still complete.
  5. Never --privileged, and treat a socket mount as a decision rather than a detail.
  6. Separate networks, so an application that is compromised cannot see a database it never needed.
  7. Drop capabilities and run read-only where the image tolerates it. Worth doing one image at a time, after checking it still works across an upgrade, rather than as a blanket change.

The first two are worth more than the rest combined and are the ones people skip.

When you actually want a virtual machine #

If you are running something you genuinely do not trust, or something that needs its own kernel modules, or you want snapshots of a whole machine rather than of a directory, a VM is the right tool and modern hardware makes it cheap enough.

For running a photo library and a file server that you chose deliberately and keep updated, containers are a reasonable boundary and an excellent packaging format.

Settings

Your values

The book is written with placeholder names so it makes sense to everybody. Put your own in and every chapter, every command and every copy-paste prompt updates to match.

Nothing here is sent anywhere. It is saved in this browser, so it comes back next time. A different browser or a private window gets the placeholders again.

Live preview

$ ssh admin@192.168.1.20
$ sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
$ sudo hostnamectl set-hostname homeserver
$ sudo timedatectl set-timezone Europe/Paris

Real commands from chapters 2, 3 and 4. They change as you type.

The account you log in as. Not root, and not necessarily the same name you use on your laptop.

Introduced in Chapter 2, Meet your server

The book's placeholder is admin

What the machine calls itself. You choose it, and it shows up in your shell prompt and your logs.

Introduced in Chapter 3, A safe front door

The book's placeholder is homeserver

The IP address your server has on your home network, from ip -brief addr.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.20

The address range and prefix shown by ip route or ip -brief addr, written in CIDR form. Copy the real prefix; do not guess /24.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.0/24

The address traffic goes to on its way out of your house, from ip route.

Introduced in Chapter 2, Meet your server

The book's placeholder is 192.168.1.1

In Region/City form, or Etc/UTC if you would rather read logs in UTC.

Introduced in Chapter 3, A safe front door

The book's placeholder is Europe/Paris

A registered name you control. Chapter 8 uses it for the LAN route; chapter 12 uses a separate private Tailscale name remotely.

Introduced in Chapter 8, One door, many rooms

The book's placeholder is example.com

The email identity allowed to administer the tagged server in your Tailscale policy.

Introduced in Chapter 10, Your own private network

The book's placeholder is you@example.com

The mailbox that should receive actionable home-server alerts.

Introduced in Chapter 14, Knowing it is alive

The book's placeholder is alerts@example.com

Once you save, the prose and the commands read with your names, the copy buttons copy your values, and the copy-paste prompts describe your machine accurately. That last one matters: an assistant told your network is 192.168.1.0/24 when it is not will send you chasing the wrong thing.

Anything you leave empty keeps the book's placeholder.