Security11 min read

Installing Docker on a server without leaving a root-shaped hole

The docker group is root by another name, the socket is a skeleton key, and automatic cleanup can delete your data — the three facts that turn a five-minute Docker install into a security decision.

Published
September 14, 2026
Reading time
11 min read
Servor Team
Security
Tags
Docker · Containers
Lire cet article en français

The install is easy. That is the problem.

Docker's convenience script — curl -fsSL https://get.docker.com | sh — works, and works so smoothly that it hides what it just did. In a few seconds you have a daemon running as root, a Unix socket that can start a container as root, and a docker group whose members can reach that socket. Every one of those is a security decision, and the script made all of them for you without asking.

So the thesis here is uncomfortable and worth saying plainly: installing Docker is not a package-management task, it is a privilege-granting one. The commands are trivial. What matters is understanding what access you just created, and choosing who gets it — because the defaults quietly grant root to more people than you probably intend.

Before you install: what is already here?

A convenience script run twice is not idempotent in your favour — it can pull a different install method than the one already on the box, leaving you with a confused mix. So look first.

  • Is Docker already installed? command -v docker && docker --version. If it is, you are not installing — you are auditing, and possibly cleaning up a duplicate.
  • Is the daemon running? systemctl is-active docker. A running daemon means containers may already be in flight; a reinstall is the last thing you want to fire blind.
  • Who is currently in the docker group? Before you add anyone, know who is already there. Every name on that list has root-equivalent power on this machine, and stale entries are exactly the forgotten door the hardening literature warns about.

1. Install the engine — and know what the daemon is

Whether you use the convenience script or the distribution's repository, the outcome you want is the same: the engine plus the Compose v2 plugin, the daemon enabled so it survives a reboot, and a proof that it actually runs containers rather than merely being installed.

That last proof is the point of the hello-world image. Anyone can install a binary; the honest verification is docker run --rm hello-world printing its success line, which exercises the whole path — daemon, socket, image pull, container start — in one shot. A version string tells you the client exists. A completed hello-world tells you the system works.

Enable the daemon (systemctl enable --now docker) and confirm active, because a daemon that runs today but does not come back after a reboot is a container host that vanishes at the worst time.

2. The docker group is root — treat it that way

Here is the fact that most install guides mention as a footnote and should print in bold: adding a user to the docker group is equivalent to giving them passwordless root. It is not “almost” root or “root for Docker things”. A member of the group can run a container that mounts the host's entire filesystem and read or write any file on it as root. There is no privilege boundary between the docker group and root; there only appears to be one.

This is why usermod -aG docker deploy is a decision, not a convenience. Grant it deliberately, to accounts you would already trust with sudo, and grant it to as few as possible. And know the mechanical detail that trips everyone up: group membership only takes effect on a newlogin session. Add yourself to the group and the current shell still cannot reach the socket — you have to log out and back in. People who do not know this often “fix” it by reaching for sudo docker forever, which hides the fact that they never needed the group at all.

If you can, prefer not to hand out the group at all. Rootless Docker and Podman exist precisely to run containers without a root daemon, and for many workloads they are the correct answer. But if you do use the group, verifying it means checking that the intended user is in it — and, just as importantly, that nobody unintended is.

3. The socket is a skeleton key

The single most consequential mistake with Docker is exposing its socket. Anything that can talk to /var/run/docker.sock can start a privileged container, and a privileged container is root on the host. This turns several popular conveniences into loaded weapons:

  • Management UIs that mount the socket. Portainer, Dockge and their kin are genuinely useful, and they work by bind-mounting the Docker socket — which means the web app you just exposed is root on the host. Never publish one to the internet without strong authentication in front of it, and prefer binding it to loopback behind a reverse proxy you control.
  • The socket mounted read-write into a container. Some stacks ask for it. Read-only is bad enough; read-write is handing the container the keys. Mount it only when you must, only read-only, and only into images you actually trust.
  • A private registry or dashboard published on 0.0.0.0. Bind internal services to 127.0.0.1 and reach them through a proxy that terminates TLS and checks auth. A registry without TLS and a password is an open invitation.

4. Automatic cleanup that does not delete your data

Docker accumulates: stopped containers, dangling images, unused build cache, and — the dangerous one — volumes. A scheduled prune keeps a busy host from filling its disk, and it is a genuinely good idea. It is also one flag away from a data-loss incident.

The rule is simple and absolute: never put --volumes in an automatic prune. Images and stopped containers are reproducible; a volume is where your database lives. An unattended docker system prune -af --volumes firing at three in the morning does not free space, it deletes state. Prune images and build cache on a timer with a until filter so recent layers survive; leave volumes to a deliberate, human decision. Verify the timer exists (systemctl list-timers) and read exactly what it will run before you trust it to run unattended.

What Docker does not do for you

A container is not a security boundary the way a virtual machine is; a kernel exploit crosses it. An image pulled from a public registry is code you did not write and did not review —latest is not a version, and a compromised base image is a compromised host. Docker isolates processes; it does not vet what runs inside them, patch it, or stop it from being exposed on a port you forgot. And nothing about installing Docker backs anything up: the volumes are yours to protect. Convenience is not a security posture.

The check that proves the posture, not just the install

In order: docker run --rm hello-world for a working engine; docker compose version for the plugin; systemctl is-enabled docker so it survives a reboot; the docker group's membership list, read as the security artefact it is; ss -tlpnto confirm no dashboard, registry or socket proxy is listening on a public interface it should not be; and a read of any prune timer's actual command line, confirming --volumes is nowhere in it.

Where Servor fits

Servor ships Docker, Compose, Portainer and the scheduled prune as recipes — part of 150+ vetted playbooks — and the recipe shape is what keeps a privilege-granting task from being careless. A recipe is not a script fired blind: it starts with reconnaissance, reading whether Docker is already installed and whether the daemon is up, so it does not stack a second install on a working one. Each step carries its own verify — the hello-world success line, the is-active, the timer listed — so the run proves it worked. The playbook is idempotent: an existing engine is checked and reported, not reinstalled.

The guardrails are written into the recipes precisely because the defaults are dangerous: the socket is mounted read-only where it is mounted at all, a management UI is never opened without auth, and the automatic prune refuses --volumes. Because adding a user to the docker group grants root-equivalent power, that step is treated as the privileged operation it is — proposed, explained, and approved by a human, not slipped through.

And the execution never leaves the zero-knowledge path: every command is signed in your browser with a key derived from your vault key, relayed verbatim, and verified by the agent on the machine before it runs. The forbidden-command list, enforced on the control plane and again on the agent, refuses the catastrophic no matter what you approve. The discipline behind that loop is the subject of the Plan-Execute-Verify piece, and the same care applied to the administrative door is in the SSH-hardening piece.

Next step

Run it, don't just read it.

Free for two servers, no card. Every command is signed in your browser before it runs — our servers relay it, they cannot forge it.

Continue reading

Archive