When deploying isolated environments using Docker on a Linux low-latency cloud VPS, many administrators assume that their standard UFW or CSF firewall rules will protect their containers.
However, a dangerous and often overlooked architectural behavior in Docker’s networking stack can silently bypass your meticulously crafted firewall rules, exposing critical internal databases and services to the public internet.
The Docker IPTables Problem
By default, when you bind a port in Docker (e.g., docker run -p 8080:80), the Docker daemon modifies the system’s iptables rules to route traffic before it reaches the UFW or CSF rulesets.
This means that even if you have a UFW rule explicitly denying traffic to port 8080, the Docker-generated PREROUTING rules in the nat table will intercept the packet and forward it directly to the container, bypassing the ufw-user-input chain entirely.
This is a critical security vulnerability, often leading to unintended Firewall Bypasses where internal MySQL or Redis instances are suddenly accessible globally.
How to Prevent Docker Firewall Bypasses
There are several methods to resolve this issue and properly secure your Docker deployments.
1. Bind to Localhost (127.0.0.1)
The simplest and most effective way to prevent public exposure is to bind the container’s ports exclusively to the localhost interface. If a container does not need to be accessed directly from the outside world (e.g., a database container placed behind an Nginx reverse proxy), bind it to 127.0.0.1:
# docker-compose.yml
services:
database:
image: mysql:8.0
ports:
- "127.0.0.1:3306:3306"
This ensures that the port is only accessible from within the VPS itself, rendering the external firewall bypass irrelevant.
2. Disabling IPTables in Docker Daemon
If you need fine-grained control over your firewall rules and prefer to manage them manually, you can instruct Docker to stop manipulating iptables.
Edit your /etc/docker/daemon.json file (create it if it doesn’t exist) and add the following configuration:
{
"iptables": false
}
Restart the Docker service:
sudo systemctl restart docker
Warning: Setting "iptables": false will break container-to-container DNS resolution and outbound internet access unless you manually configure the necessary NAT and masquerade rules in your firewall. This approach is highly technical and usually only recommended for advanced Linux administrators managing bare-metal hypervisors or strict Compliance Frameworks.
3. Using DOCKER-USER Chain
The official recommendation from Docker is to insert your custom firewall rules into the DOCKER-USER iptables chain. Rules placed in this chain are evaluated before Docker’s automatic routing rules.
For example, to drop all external connections to container ports except from a specific trusted IP (like a WAF or a corporate VPN):
# Allow connections from trusted IP
iptables -I DOCKER-USER -i eth0 -s 203.0.113.50 -j RETURN
# Drop all other external connections
iptables -I DOCKER-USER -i eth0 -j DROP
Conclusion
Docker is an incredibly powerful tool for isolated deployments, but its default networking behaviors prioritize convenience over strict security. When deploying containers on a public-facing Linux VPS, always audit your iptables routing to ensure your internal services aren’t accidentally exposed to the world.
