How to Restrict Docker Container External Access Using iptables: A Practical Guide for Securing Port 3306


2 views

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