SERVER SECURITY GUIDE

Docker Host Security on a VPS: Published Ports, the Docker Socket and Rootless Mode

Docker makes deployment easy, and that ease hides three facts that catch small-server owners out. A published port is open to the internet even if your firewall says otherwise. Anything that can talk to the Docker daemon is effectively root on the host. And a container is not a security boundary you should lean on without further settings. This guide covers the host side: how Docker interacts with your firewall, who can control the daemon, and which container flags to avoid. It is deliberately about configuration, not about any single attack; for a recent real-world example of attackers using Docker hosts after an application flaw, see the KEV patch priority article. For the broader baseline, start with the VPS security checklist.

Quick win (10 minutes)

  • Run docker ps --format "table {{.Names}}\t{{.Ports}}" and look for 0.0.0.0 or [::] in the ports column.
  • For every service that only your reverse proxy or other containers need, change the mapping to 127.0.0.1:HOST:CONTAINER.
  • Run ss -tlnp | grep -E "2375|2376". Nothing should be listening on the Docker API ports.
  • Search your compose files for /var/run/docker.sock and privileged: true, and note every match.
  • Run getent group docker and confirm the only members are people you would trust with root.

Published ports bypass your firewall

Many people configure ufw, allow only SSH and HTTPS, and assume the job is done. Then they run a container with -p 8080:80 and the port is reachable from anywhere. The Docker documentation describes why: Docker routes container traffic in the nat table, so packets are diverted before they reach the firewall rules ufw manages, and, in the documentation's words, "packets are routed before the firewall rules can be applied, effectively ignoring your firewall configuration."

Docker also states plainly that "publishing container ports is insecure by default," and that without a specific host address the daemon publishes ports "to all host addresses (0.0.0.0 and [::])." The practical effect: a database, admin dashboard or message queue you published for convenience is exposed to the internet while ufw status shows nothing wrong. Docker also advises that you should not modify the rules it creates, which is why the fixes below work around them instead.

Bind to localhost and put a proxy in front

The simplest fix is to publish only to the loopback address. The Docker documentation gives this form for restricting a published port to the host itself:

docker run -p 127.0.0.1:8080:80 -p '[::1]:8080:80' nginx

In a compose file the equivalent is "127.0.0.1:8080:80" under ports. Then put a reverse proxy such as nginx on the host, listening on 80 and 443, that forwards to those local ports, so the only public entry points are ones you configured deliberately and can protect with sensible nginx settings. For services that only talk to each other, skip publishing altogether and put them on a user-defined Docker network. Databases are the case that matters most: see how to find and close exposed databases.

Filter with the DOCKER-USER chain

When a port really must be public, but only for certain addresses, filter in the chain Docker provides for that purpose. The documentation describes DOCKER-USER as a placeholder for user-defined rules that are "processed before rules in the DOCKER-FORWARD and DOCKER chains." Its example drops traffic from every address except one:

iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.10 -j DROP

Two details from the same page save hours of debugging. First, the documentation warns you may need to allow responses from servers outside the permitted ranges, using iptables -I DOCKER-USER -m state --state RELATED,ESTABLISHED -j ACCEPT, placed before the drop rules. Second, packets in this chain have already had their destination rewritten, so to match the original address and port you must use the conntrack extension, which, per the docs, may degrade performance. Replace eth0 with your public interface, test from a second connection and keep your SSH session open while you do. Rules added this way are not persistent across reboots until you save them with your distribution's tooling.

The Docker socket is root

The daemon listens on a Unix socket, /var/run/docker.sock. Whoever can write to it controls the daemon, and the daemon runs as root. The Docker security documentation shows why that is serious: you can start a container where the host's / is mounted inside it, after which "the container can alter your host filesystem without any restriction." The same page says that "only trusted users should be allowed to control your Docker daemon."

Two consequences follow. Membership of the docker group is root: Docker's own post-install notes say the group "grants root-level privileges to the user," so treat it like sudo and keep it to people you would trust with root (see users, sudo and least privilege). And mounting the socket into a container, which many monitoring tools and CI runners ask for, hands that container the host. The OWASP Docker Security Cheat Sheet is blunt: giving someone access to the socket "is equivalent to giving unrestricted root access to your host," and mounting it read-only "does not make the Docker API read-only." If a tool insists on the socket, run it only if you trust it as much as root, or use a socket proxy that exposes only the API calls it needs. The glossary entry has the short version.

Not sure which of your ports are really public?

A free, read-only server check lists the services reachable from the internet, including those Docker published, without changing anything.

Get your free server check Talk to a security engineer

Never expose the daemon API without TLS

The daemon can also listen on TCP so you can manage it remotely. The documentation lists the default ports as 2376 if you use TLS and 2375 otherwise, warns that remote access can leave you "vulnerable to unauthorized access to the host and other attacks," and that "remote non-root users" may be able to "gain root access on the host." It also says that remote access without TLS is not recommended and "will require explicit opt-in in a future release." The security guide goes further: "it is mandatory to secure API endpoints with HTTPS and certificates."

For one VPS you rarely need this at all. If you want to run Docker commands from your laptop, create a context over SSH (docker context create vps --docker "host=ssh://user@your-server"), which reuses your hardened SSH setup and key authentication instead of opening a new door. Confirm with the quick-win command above that nothing is listening on 2375 or 2376.

Container flags that matter

The OWASP cheat sheet gives a short list that covers most of the risk:

  • Do not run with --privileged. Docker's reference says the flag "gives all capabilities to the container" and enables access to all devices on the host. It removes most of the isolation you were relying on.
  • Set a non-root user. OWASP calls an unprivileged user "the best way to prevent privilege escalation attacks." Use USER in the image or user: in compose.
  • Drop capabilities. "The most secure setup is to drop all capabilities --cap-drop all and then add only required ones."
  • Block privilege gain. Run containers with --security-opt=no-new-privileges.
  • Keep the host up to date. OWASP's first rule is that containers share the host's kernel, so a vulnerable kernel is a vulnerable container. Follow your patch routine for the host as well as the images.

In a compose service these look like user: "1000:1000", cap_drop: [ALL] and security_opt: ["no-new-privileges:true"].

Rootless mode and patching

If you can accept its constraints, rootless mode shrinks the damage from the worst case. The Docker documentation says it lets you run "the Docker daemon and containers as a non-root user" to mitigate potential vulnerabilities in the daemon and the container runtime. It requires newuidmap and newgidmap (usually from the uidmap package) and at least 65,536 subordinate UIDs and GIDs for the user in /etc/subuid and /etc/subgid. OWASP adds a caveat worth remembering: rootless mode does not protect against host kernel vulnerabilities.

Finally, make Docker part of routine hygiene. Pin image versions instead of using latest, rebuild images when base layers are patched, remove containers and images you no longer use, and review docker ps -a now and then for anything you did not deploy. Unexpected containers are a classic sign of compromise, covered in the breach response guide, and because a Docker host should also have its logs watched, the monitoring guide is a natural next step.

If a tool must reach the Docker API, a Docker socket proxy limits which calls it can make, though it does not make the socket safe by itself.

Frequently asked questions

Why does Docker ignore my ufw rules?

Docker routes published-port traffic through the nat table, so packets are diverted before they reach the rules ufw manages. Per the Docker documentation, the firewall configuration is effectively ignored for those ports. Bind to 127.0.0.1 or filter in the DOCKER-USER chain instead.

How do I stop a Docker container being reachable from the internet?

Publish it to the loopback address only, for example -p 127.0.0.1:8080:80, or do not publish it at all and put it on a user-defined network. Then expose only a reverse proxy on ports 80 and 443.

Is it safe to mount the Docker socket into a container?

Generally no. Access to the socket is equivalent to unrestricted root access to the host, and mounting it read-only does not make the Docker API read-only. Use it only for software you trust as much as root, or put a socket proxy in between.

Is adding a user to the docker group safe?

Treat it as giving them root. Docker states that the docker group grants root-level privileges to the user, so restrict it to people you would trust with sudo.

What ports does the Docker API use, and should I open them?

The default is 2376 with TLS and 2375 without. Do not expose them publicly. For remote management on a single VPS, use a Docker context over SSH instead, which reuses your SSH hardening.

Does rootless Docker make my server safe?

It reduces the impact of vulnerabilities in the daemon and runtime by running them without root, but it does not protect against host kernel vulnerabilities. Keep it as one layer alongside patching, firewalling and dropping container capabilities.

Not sure where your server stands?

Get a free, read-only security check, or talk to a security engineer about managed protection.

Get your free server check Talk to a security engineer