When running MySQL inside a Docker container with port forwarding (-p 3306:3306
), many administrators discover their MySQL instance becomes accessible from external networks despite having restrictive iptables rules. This behavior occurs because Docker manipulates the host system's networking stack in specific ways.
# Typical Docker run command causing the issue
docker run -p 3306:3306 asyncfi/magento-mysql
Docker creates custom chains in iptables to manage container networking. When examining iptables -L -v --line-numbers
, you'll notice Docker-related chains:
Chain FORWARD (policy ACCEPT)
target prot opt in out source destination
ACCEPT all -- docker0 docker0 anywhere anywhere
ACCEPT all -- docker0 !docker0 anywhere anywhere
ACCEPT all -- any docker0 anywhere anywhere ctstate RELATED,ESTABLISHED
Docker uses NAT rules that bypass traditional INPUT chain filtering. These rules are added to the NAT table:
# View Docker's NAT rules
iptables -t nat -L -n
You'll typically see rules like:
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 to:172.17.0.2:3306
To properly secure your Dockerized MySQL instance, consider these approaches:
Solution 1: Restrict Docker's NAT Rules
# Only allow localhost access
iptables -t nat -R DOCKER 1 -p tcp -m tcp --dport 3306 -j DNAT \
--to-destination 172.17.0.2:3306 -s 127.0.0.1
Solution 2: Modify Docker's Forward Policy
# Change Docker's default FORWARD policy
iptables -P FORWARD DROP
iptables -I FORWARD -i docker0 -o eth0 -j DROP
Solution 3: Use Docker's Native Restriction
# Only bind to localhost
docker run -p 127.0.0.1:3306:3306 asyncfi/magento-mysql
Newer Docker versions (1.13+) provide better control through the --iptables
flag and user-defined networks:
# Create an isolated network
docker network create --internal secure-mysql-net
# Run container in isolated network
docker run --network secure-mysql-net -p 3306:3306 asyncfi/magento-mysql
For production environments, implement layered security:
# 1. Bind only to localhost
docker run -p 127.0.0.1:3306:3306 asyncfi/magento-mysql
# 2. Add iptables rules
iptables -I DOCKER-USER -i eth0 -p tcp --dport 3306 -j DROP
# 3. Configure MySQL bind address
RUN sed -i -e"s/^bind-address\s*=\s*0.0.0.0/bind-address = 172.17.0.2/" /etc/mysql/my.cnf
When running MySQL in a Docker container with port mapping (-p 3306:3306
), many developers are surprised to find the port remains accessible externally despite iptables rules that should block it. This behavior stems from Docker's networking architecture and how it interacts with the host firewall.
Docker creates custom iptables rules when it starts containers with port mappings. These rules typically live in the DOCKER
chain which processes packets before reaching your INPUT
chain. Here's what actually happens:
# Docker's default forwarding rule (simplified)
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -p tcp -m tcp --dport 3306 -j ACCEPT
While your custom rules in the INPUT
chain block traffic, Docker's rules in the FORWARD
chain allow it. To see the full picture:
# View all chains with Docker rules
iptables -L -n -v --line-numbers
iptables -t nat -L -n -v --line-numbers
Here are three effective approaches to properly secure your MySQL container:
1. Docker-Aware Firewall Rules
Insert rules above Docker's default rules in the DOCKER
chain:
# Block MySQL externally while allowing localhost
iptables -I DOCKER -i eth0 -p tcp --dport 3306 -j DROP
iptables -I DOCKER -i lo -p tcp --dport 3306 -j ACCEPT
2. Network Namespace Isolation
Create a custom network namespace for stricter isolation:
# Create and enter new network namespace
ip netns add mysql-ns
ip netns exec mysql-ns bash
# Start container in the namespace
docker run --net=container:$(hostname) -p 3306:3306 asyncfi/magento-mysql
3. Docker's Built-in Firewall Features (Recommended)
Modern Docker versions support more granular control:
# Create a custom bridge network with internal flag
docker network create --internal secure-mysql-net
# Run container in the isolated network
docker run --network=secure-mysql-net asyncfi/magento-mysql
For newer Docker versions (1.10+), consider this approach:
# Create a user-defined bridge network
docker network create --driver=bridge --subnet=192.168.100.0/24 mysql-net
# Run container with specific IP
docker run --network=mysql-net --ip=192.168.100.2 asyncfi/magento-mysql
# Add iptables rules specific to this network
iptables -A INPUT -s 192.168.100.0/24 -p tcp --dport 3306 -j DROP
Always test your firewall rules from multiple perspectives:
# From external machine (should fail)
telnet your-server-ip 3306
# From host (should succeed)
telnet 127.0.0.1 3306
# From another container (depends on your rules)
docker run --rm -it busybox telnet mysql-container-ip 3306