Debian 12 Bookworm: Docker Port Mapping Ignored on Bridge Network Troubleshooting
Experiencing Docker host port mapping failures on Debian 12 Bookworm's bridge network? This guide details root causes and step-by-step `nftables` and Docker configuration fixes.
Experiencing Docker host port mapping failures on Debian 12 Bookworm's bridge network? This guide details root causes and step-by-step `nftables` and Docker configuration fixes.
When deploying Docker containers on Debian 12 (Bookworm), you might encounter a frustrating issue where your specified host port mappings (-p 8080:80) are seemingly ignored, making your services inaccessible from outside the host machine, even though the containers appear to be running correctly. This often manifests despite docker ps showing the correct port bindings and docker-proxy listening on the host. This guide will delve into the underlying causes, specifically concerning Debian 12's default use of nftables, and provide a highly technical, step-by-step resolution.
Symptom & Error Signature
The primary symptom is a lack of connectivity to Docker-mapped ports from external networks, or even from the host machine's external IP address, despite all appearances indicating correct configuration.
Typical observations:
Container running with correct port mapping:
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1b2c3d4e5f6 nginx:latest "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp webserverHere,
0.0.0.0:8080->80/tcpindicates Docker should be listening on port8080on all host interfaces and forwarding to container port80.docker-proxyis listening on the host:$ sudo ss -tulnp | grep 8080 tcp LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=12345,fd=4)) tcp LISTEN 0 4096 [::]:8080 [::]:* users:(("docker-proxy",pid=12345,fd=5))This confirms
docker-proxyhas bound to the specified port on the host.Localhost access works, external access fails:
$ curl http://localhost:8080 # This works <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ...$ curl http://YOUR_SERVER_IP:8080 # This times out or connection refused curl: (7) Failed to connect to YOUR_SERVER_IP port 8080 after X ms: Connection timed outiptablesrules appear correct:$ sudo iptables -t nat -L -n -v # ... 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:8080 to:172.17.0.2:80 # ...Docker has successfully added the necessary
DNAT(Destination Network Address Translation) rule to redirect traffic.
Despite these checks, external connections are blocked.
Root Cause Analysis
The core of this issue on Debian 12 (Bookworm) stems from its transition to nftables as the default packet filtering framework, replacing the traditional iptables-legacy. While Docker primarily interacts with the netfilter subsystem via iptables commands (which on Debian 12 are typically aliased to iptables-nft, a compatibility layer), the underlying nftables ruleset can override or preempt Docker's rules, especially concerning default policies or specific chain priorities.
Here's a breakdown of the underlying reasons:
nftablesDefault Policies: Debian 12'snftablesconfiguration, especially iffirewalldorufware not explicitly managing it, often defaults to apolicy dropon critical chains likefilter inputandfilter forward.- Docker's
netfilterInteraction: When you map a host port (e.g.,-p 8080:80), Docker performs two key actions:- It starts a
docker-proxyprocess that listens on the host's0.0.0.0:8080. - It adds
iptablesNATrules to redirect incoming traffic destined forHOST_IP:8080to the container's IP and port (e.g.,172.17.0.2:80). ThisDNAToccurs in thenat PREROUTINGchain. - It adds
iptablesFILTERrules (in theFORWARDchain) to allow the forwarded packet to reach the container.
- It starts a
nftablesPrecedence: Even thoughiptables-nftaims for compatibility,nftablesrules can have higher precedence or be evaluated earlier in the packet processing pipeline, especially if defaultinputorforwardpolicies are set todrop. A packet destined forHOST_IP:8080(beforeDNAT) or forCONTAINER_IP:80(afterDNAT) might be dropped bynftablesbefore it hits theiptableschain where Docker'sACCEPTrules reside, ornftablesmight simply have a broaderdroprule.- Specifically, after the
DNATin thenat PREROUTINGchain, the packet is now destined for the container. It then enters thefilter FORWARDchain. Ifnftableshas adroppolicy on this chain and no explicitacceptrule for traffic originating externally and destined for thedocker0bridge (or specific container IPs), the packet will be dropped.
- Specifically, after the
- Inconsistent Firewall Management: If you have multiple firewall management tools (e.g.,
firewalldandufw) installed or partially configured, or ifnftablesrules are manually managed in addition to Docker'siptablesrules, conflicts can arise.
Step-by-Step Resolution
This section outlines the most robust and recommended approaches to resolve Docker port mapping issues on Debian 12. We prioritize nftables configuration as it is the default.
1. Verify Docker Network Configuration
First, ensure Docker's internal networking is configured to manage iptables rules.
Check
daemon.json: Ensure Docker is configured to manageiptablesrules. By default, it is. If you have/etc/docker/daemon.json, make sure it does not contain"iptables": false. If it does, remove or set it totrue.{ "iptables": true }If you modified it, restart Docker:
sudo systemctl restart dockerConfirm Docker's
iptablesRules: Check thenatandfiltertables for Docker-related rules.sudo iptables -t nat -L -n -v sudo iptables -L -n -v # Checks filter tableYou should see
DOCKERchains withDNATrules in thenattable andACCEPTrules in thefilter FORWARDchain targeting thedocker0bridge.
2. Identify the Active Firewall Solution
Debian 12 primarily uses nftables, but it can be managed by firewalld, ufw, or directly. Determining which is active is crucial.
Check
firewalldstatus:sudo systemctl status firewalldIf it's
active (running),firewalldis managing your firewall.Check
ufwstatus:sudo ufw statusIf it's
Status: active,ufwis managing your firewall.Check
nftablesdirectly: If neitherfirewalldnorufware active, your system is likely managingnftablesdirectly.sudo nft list rulesetThis command displays the entire active
nftablesruleset. Look fortable ip filterand chains likeinputandforward. Pay close attention to their policies (e.g.,policy drop;).Never blindly disable your firewall without understanding the security implications. This can expose your server to significant risks.
3. Configure the Active Firewall to Allow Docker Traffic
Proceed based on your active firewall.
If firewalld is Active
firewalld on Debian 12 uses nftables as its backend. You need to open the specific host ports in firewalld.
# Example: Allow TCP traffic on port 8080
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
# If you have other Docker-mapped ports, add them too (e.g., 8443)
sudo firewall-cmd --zone=public --add-port=8443/tcp --permanent
# Reload firewalld to apply changes
sudo firewall-cmd --reload
This should allow incoming connections to your Docker-mapped ports. firewalld should correctly handle the interaction with Docker's iptables rules.
If ufw is Active
ufw (Uncomplicated Firewall) also leverages nftables on Debian 12.
# Example: Allow TCP traffic on port 8080
sudo ufw allow 8080/tcp
# If you have other Docker-mapped ports, add them too (e.g., 8443)
sudo ufw allow 8443/tcp
# Reload ufw (or disable/enable) to apply changes
sudo ufw reload # Or: sudo ufw disable && sudo ufw enable
ufw will create the necessary nftables rules.
If nftables is Directly Managed
This is the most likely scenario on a minimal Debian 12 installation without firewalld or ufw. You need to explicitly add rules to your nftables configuration. The key is to ensure the filter forward chain permits traffic to your Docker bridge interface.
Backup current
nftablesconfig:sudo cp /etc/nftables.conf /etc/nftables.conf.bakEdit
/etc/nftables.conf: Open the configuration file for editing:sudo nano /etc/nftables.confLocate your
table ip filterblock. If achain forwardexists, modify it. If not, create it. The critical part is to explicitly allow traffic that is being forwarded to and from thedocker0bridge interface.Add or modify your
filter forwardchain similar to this example (adjustingpolicy dropif your existing policy isaccept):table ip filter { chain input { type filter hook input priority 0; policy drop; # Common default policy # ... (existing rules for localhost, SSH, established connections) ... iif "lo" accept ct state established,related accept tcp dport 22 accept # Allow SSH # Add any other specific host services here (e.g., web server running directly) # Example: tcp dport { 80, 443 } accept # If Nginx/Apache runs directly } chain forward { type filter hook forward priority 0; policy drop; # <--- THIS IS OFTEN THE CULPRIT ct state established,related accept # Allow traffic FROM the docker0 bridge interface (container to external) iifname "docker0" accept # Allow traffic TO the docker0 bridge interface (external to container via Docker NAT) oifname "docker0" accept # For IPv6 Docker configurations, you might also need similar rules in `table ip6 filter` } chain output { type filter hook output priority 0; policy accept; # Common default policy ct state established,related accept # ... (other output rules) ... } }Apply and persist
nftablesrules:sudo nft -f /etc/nftables.conf # Enable the nftables service to load rules on boot sudo systemctl enable nftables sudo systemctl restart nftablesAfter modifying
nftables.conf, it's critical to test connectivity from an external machine toYOUR_SERVER_IP:YOUR_DOCKER_PORT. Usenc -vz YOUR_SERVER_IP YOUR_DOCKER_PORTorcurl. If you lose SSH access, you may need console access to revert changes.
4. Alternative: Revert to iptables-legacy (Less Recommended)
While nftables is the modern approach, if you encounter persistent issues, or if you have complex legacy iptables configurations, you can force Docker to use iptables-legacy.
Switching to
iptables-legacycan have implications if other services on your system are configured to interact specifically withnftables. This should be considered a last resort or for specific compatibility needs, as it deviates from Debian 12's default.
Configure
update-alternatives:sudo update-alternatives --config iptables # Select the option corresponding to 'iptables-legacy' sudo update-alternatives --config ip6tables # Select the option corresponding to 'ip6tables-legacy'You will be presented with a list like:
Selection Path Priority Status ------------------------------------------------------------ * 0 /usr/sbin/iptables-nft 90 auto mode 1 /usr/sbin/iptables-legacy 50 manual mode 2 /usr/sbin/iptables-nft 90 manual mode Press <enter> to keep the current choice[*], or type selection number:Type
1and press Enter foriptables-legacy. Repeat forip6tables.Restart Docker:
sudo systemctl restart dockerDocker will now configure rules using the
iptables-legacybinaries, which might behave more predictably in some scenarios.
5. Verify Connectivity
After applying any of the above changes, re-test access to your Docker-mapped ports:
# From an external machine or different host
curl http://YOUR_SERVER_IP:8080
# You can also use netcat for a quick port check
nc -vz YOUR_SERVER_IP 8080
A successful connection or "Connection to YOUR_SERVER_IP 8080 port [tcp/*] succeeded!" indicates the issue is resolved.
Our Production Verification Guarantee
Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.