Docker on Ubuntu with IPTables

NAT TABLE

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere            !localhost/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        anywhere
MASQUERADE  tcp  --  172.17.0.2           172.17.0.2           tcp dpt:http

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
DNAT       tcp  --  anywhere             anywhere             tcp dpt:http to:172.17.0.2:80

FILTER TABLE

Chain FORWARD (policy DROP)
target     prot opt source               destination
DOCKER-USER  all  --  anywhere             anywhere
DOCKER-ISOLATION-STAGE-1  all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
InstanceServices  all  --  anywhere             link-local/16

Chain DOCKER (1 references)
target     prot opt source               destination
ACCEPT     tcp  --  anywhere             172.17.0.2           tcp dpt:http

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target     prot opt source               destination
DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target     prot opt source               destination
DROP       all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere

Chain DOCKER-USER (1 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere

Here’s my VPS with Ubuntu and Docker. There’s a testing Nginx container serving the port 80. Based on the FORWARD chain, the rules firstly entered the the DOCKER-USER which is empty. Then it proceed to DOCKER-ISOLATION-STAGE-1 which wrapped with DOCKER-ISOLATION-STAGE-2. In the DOCKER-ISOLATION-STAGE-2 all traffics are dropped. But why the Nginx Welcome page is still reachable? I have read the iptables docs and trying with all LLMs including ChatGPT, Gemini and Claude, but can’t figure it out :frowning: .

Do you want to learn about iptables or do you have a problem with a container port which shouldn’t be available but it is? By the way Docker Desktop won’t create iptables rules on your host (or maybe it will, I actually don’t know, but not these) :).

I’m not using the Docker Desktop but the normal Docker for ubuntu servers. I want to learn about the iptables that how Docker are working with it, because the behavior and the rules are not making sense to me(I can’t figure it out).

Then make sure you create your topics in the right category. I moved it to Docker Engine.

Regarding the rules, I’m not so good at iptables, but I will check it when it is not so late as it is for me now. Maybe you will get an answer before that.

1 Like

Thank you sir! But sorry for the improper topic.

When I first saw your iptables rules I didn’t understand it either, than I relized some information was missing. There are a couple of ways to get the rules and it seems you ran iptables -t nat -L and iptables -t filter -L

That will show you the source IP addresses and destination IP addresses or “anywhere” instead of 0.0.0.0/0. But what is missing is the interfaces. The flag -n will show you the ip addresses as numbers even if it means "anywhere" and the flag -v` will enable verbose mode which will show more columns like in and out interfaces. For example for me in a virtual machine where only a portainer container is running.

iptables -t filter -vnL

Output:

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.30.0.2           tcp dpt:9000

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER-ISOLATION-STAGE-2  all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

That shows in the isolation stage 1 there is only a return rule to go back to where the isolation stage 1 chain was called, and another rule to go to isolation stage 2 only when the in interface is docker0 but the out interface is not. It just means only when a process in a container on the default docker bridge requests something from outside that network.In the isoaltion stage 2 it drops packages only when the out interface is docker0, but in the previous stage it just filtered that case out. So packages will be dropped only when the isolation stage 2 was called from somewhere else or when isolation stage 1 is changed and it allows docker0 as out interface. So basically nothing happens here. These seems to be default rules until something changes them to restrict access.

Other ways to get the rules that might be more familiar to you if you wrote iptables rules:

  1. use -S instead or -L
    iptables -t filter -S
    
    Using -v is not necessary as it would just add -c 0 0 which doesn’t help to undestand the rules better and -n is not supported as this returns lines that you could use as parameters of iptables.
  2. If you installed iptables-persistent, you can run
    iptables-save
    
    or
    iptables-save -t filter
    
    or get only the filter table. This will return an output that could be saved in a file that you can use to restore
    the rules using iptables-restore.

When I tried to refresh my memory about iptables, I found this blogpost which is a good summary of iptables

It includes a picture in svg format (if you open it in a new tab, you can zoom in) from wikipedia

That picture is something I don’t fully understand. It rquires deeper knowledge about the network layers

Oh wow, thanks for the reply! This is so helpful, I see how I missed the important parts. Thanks a lot sir.