When you expose a Docker container port like 3306 using -p 3306:3306
, Docker automatically creates complex NAT rules in the iptables DOCKER
chain. This bypasses the standard INPUT
chain where most firewall rules are traditionally placed.
First, let's examine Docker's default rules (as root/sudo):
iptables -t nat -L -n -v iptables -t filter -L DOCKER -n -v
You'll see rules like this in the NAT table:
Chain DOCKER (2 references) pkts bytes target prot opt in out source destination 0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 to:172.17.0.2:3306
We need to modify both the DOCKER-USER
chain (for filter table) and potentially the POSTROUTING
chain (for NAT). Here's the complete solution:
# Flush existing rules in DOCKER-USER chain iptables -F DOCKER-USER # Set default policy for DOCKER-USER iptables -I DOCKER-USER -j RETURN # Allow established connections iptables -I DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # Allow specific IPs to access port 3306 iptables -I DOCKER-USER -p tcp --dport 3306 -s 4.4.4.4 -j ACCEPT iptables -I DOCKER-USER -p tcp --dport 3306 -s 8.8.8.8 -j ACCEPT # Block all other traffic to port 3306 iptables -I DOCKER-USER -p tcp --dport 3306 -j DROP
To ensure rules survive reboots and Docker restarts:
# For Ubuntu/Debian: apt-get install iptables-persistent netfilter-persistent save # For CentOS/RHEL: yum install iptables-services service iptables save
If you prefer to bind the container to a specific interface:
docker run -d -p 192.168.1.10:3306:3306 mysql
Then add iptables rules for that specific IP:
iptables -A INPUT -p tcp -d 192.168.1.10 --dport 3306 -s 4.4.4.4 -j ACCEPT iptables -A INPUT -p tcp -d 192.168.1.10 --dport 3306 -s 8.8.8.8 -j ACCEPT iptables -A INPUT -p tcp -d 192.168.1.10 --dport 3306 -j DROP
Test your rules from allowed and blocked IPs:
# From allowed IP (should work): nc -zv 80.80.80.80 3306 # From other IP (should fail): nc -zv 80.80.80.80 3306
Check packet counters to verify the rules are working:
iptables -t filter -L DOCKER-USER -n -v
When running exposed Docker containers, the default networking behavior often bypasses traditional iptables INPUT chain rules. Docker installs its own rules in the NAT table that forward traffic directly to containers, making standard firewall approaches ineffective.
Docker creates several custom chains in the NAT table:
Chain DOCKER (2 references) target prot opt source destination DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 to:172.17.0.2:3306
This explains why your INPUT chain rules are being ignored - the traffic never reaches them.
Here's the complete process to restrict MySQL container access:
Step 1: Create a Dedicated Docker Chain
sudo iptables -N DOCKER-FILTER sudo iptables -I FORWARD -j DOCKER-FILTER
Step 2: Add Your Filtering Rules
# Allow specific IPs sudo iptables -A DOCKER-FILTER -s 4.4.4.4 -p tcp --dport 3306 -j ACCEPT sudo iptables -A DOCKER-FILTER -s 8.8.8.8 -p tcp --dport 3306 -j ACCEPT # Block all others sudo iptables -A DOCKER-FILTER -p tcp --dport 3306 -j DROP
Step 3: Persist the Rules
On Ubuntu/Debian:
sudo apt-get install iptables-persistent sudo netfilter-persistent save
For stricter control, bind the container to your private IP:
docker run -d -p 192.168.1.10:3306:3306 mysql
Then apply standard iptables rules:
sudo iptables -A INPUT -i eth1 -p tcp --dport 3306 -s 4.4.4.4 -j ACCEPT sudo iptables -A INPUT -i eth1 -p tcp --dport 3306 -s 8.8.8.8 -j ACCEPT sudo iptables -A INPUT -i eth1 -p tcp --dport 3306 -j DROP
- Docker may overwrite rules during service restart
- Consider using Docker's
--iptables=false
flag for full control - Always test rules with
iptables -L -v -n