This is By Design.
When you are testing from your local machine, the system will directly send on the 'interface' the network traffic.
For example, this is my system with LXD containers on a subnet called "InternalDHCP":
2: wlp59s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
inet 172.18.1.0/16 brd 172.18.255.255 scope global dynamic noprefixroute wlp59s0
5: InternalDHCP: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
inet 10.73.252.1/24 scope global InternalDHCP
valid_lft forever preferred_lft forever
If I am sending any traffic to the LXD subnet here of 10.73.252.1 and I don't have any DROP rules in the INPUT chain to block the traffic to that subnet then the routes are obeyed. The routing table looks like this for these two interfaces:
default via 172.18.0.1 dev wlp59s0 proto dhcp metric 600
10.73.252.0/24 dev InternalDHCP proto kernel scope link src 10.73.252.1
172.18.0.0/16 dev wlp59s0 proto kernel scope link src 172.18.1.0 metric 600
Basically, what this means in plain speech is:
- If INPUT and OUTPUT chains do not have any rules dropping traffic to the destination IP address, obey routing rules.
- If the destination is in the 172.18.0.0/16 subnet: send directly through
wlp59s0
device link
- If the destination is 10.73.252.0/24 subnet: send directly through
InternalDHCP
device link
- For all other traffic, route traffic to destination via 172.18.0.1 (router).
So if I am pinging 10.73.252.25 (which is a container on my system running a Debian system image), then because I don't have a rule denying transmission or receiving from that subnet in the OUTPUT and INPUT rulesets in iptables, it takes the packet of the ping and drops it directly on the InternalDHCP device/bridge on my computer, and bypasses all other behavior.
This would be analogous to my pinging 127.0.0.1 (localhost) in that it never leaves the system and in a default ufw
setup you will not be forbidden to reach local subnets (whether actually physically on your network/wifi or whether they're virtual and exist solely within your computer) and as such never is blocked.
The UFW DENY INCOMING rule is for packets originating from outside your device's direct network connections in which no permit rules have been added - but what Docker does is it adds UFW rules to allow your device to communicate with the containers without affecting your traffic (transparently!) and that is why you can still reach your internal device (and why when you send PINGs out to other devices you receive messages back - RELATED,ESTABLISHED traffic is permitted under the hood). (LXD does similar behavior allowing communication with the bridges on my computer as well)