Docker Host Port Mapping Ignored on Bridge Network with Alpine Linux Containers
Troubleshoot Docker host port mapping issues when containers running on Alpine Linux disregard bridge network configurations. Learn the root causes and step-by-step fixes.
Troubleshoot Docker host port mapping issues when containers running on Alpine Linux disregard bridge network configurations. Learn the root causes and step-by-step fixes.
Introduction
You've deployed a Docker container, often based on Alpine Linux, and meticulously configured host port mappings using docker run -p <HOST_PORT>:<CONTAINER_PORT> or the ports: directive in your docker-compose.yml. The container starts successfully, docker ps shows the ports seemingly mapped, and the application inside the container is listening on the expected port. Yet, despite all indications, you cannot access the service from the host machine or externally via the mapped port. Attempts to connect result in "Connection Refused" or "Host Unreachable" errors, as if the port mapping simply doesn't exist on the host network stack.
This guide delves into a common, yet often perplexing, scenario where Docker's iptables rules, crucial for host port forwarding on bridge networks, fail to be correctly applied or interpreted, particularly when interacting with Alpine Linux-based containers on certain host environments.
Symptom & Error Signature
The primary symptom is a lack of network connectivity to the published host port, despite Docker reporting the port mapping as active. There isn't a direct "error code" from Docker, but rather a behavioral discrepancy.
Expected docker ps output (looks correct):
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 alpine/nginx:1.24.0 "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp my-alpine-web
Attempted connectivity from host (fails):
$ curl http://localhost:80
curl: (7) Failed to connect to localhost port 80: Connection refused
$ telnet localhost 80
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused
Verification of listening ports on host (missing):
$ sudo ss -tulpn | grep :80
# Expected output (but is missing):
# tcp LISTEN 0 4096 0.0.0.0:80 0.0.0.0:* users:(("docker-proxy",pid=12345,fd=4))
# This command will return no results for port 80, indicating the host is not listening.
Root Cause Analysis
The core of this issue often lies in a subtle incompatibility or misconfiguration regarding iptables backend modes between the Docker host and how Docker itself attempts to manage network address translation (NAT) rules.
iptables-legacyvs.nftablesBackend Mismatch:- Historically, Linux systems used
iptables(now referred to asiptables-legacy). Modern distributions, including Alpine Linux internally and many host systems (e.g., Debian 10+, Ubuntu 20.04+), have transitioned tonftablesas the default packet filtering framework. - The
iptablescommand-line utility on these newer systems often acts as a frontend fornftables(iptables-nft), translatingiptablessyntax intonftablesrules. - Docker, depending on its version and the host's configuration, might still prefer or operate in
iptables-legacymode when creating itsDOCKERandDOCKER_FORWARDchains andDNATrules for port mappings. - If the host's
iptablesis configured to use thenftablesbackend, but Docker is attempting to insertiptables-legacyrules (or vice-versa, or if the translation layer is buggy), the necessaryPREROUTINGandOUTPUTchain rules for host port forwarding may not be correctly established in the active kernel's firewall. The kernel effectively ignores rules inserted by a different or incompatibleiptablesbackend.
- Historically, Linux systems used
net.ipv4.ip_forwardDisabled:- For Docker's bridge networking and port forwarding to function, IP forwarding must be enabled on the host kernel. While Docker typically enables this upon startup if not already, misconfigurations or aggressive system hardening can disable it, preventing packets from being forwarded from the host interface to the Docker bridge and vice-versa.
Conflicting Firewall Rules (Less Common for this specific symptom):
- While
firewalldorufwon the host can interfere, they usually block all traffic to Docker containers unless explicitly configured. The specific symptom ofdocker psshowing the port mapping but no host listener often points more directly to aniptablesbackend issue rather than a high-level firewall blocking all traffic.
- While
Alpine Linux containers are frequently involved because they are minimal, and issues related to the host's networking stack become more pronounced when the container itself makes no special network demands. The problem isn't inside the Alpine container, but rather in the host's ability to expose the container's ports.
Step-by-Step Resolution
The primary fix involves aligning the iptables backend used by Docker with the one active on your host system.
1. Verify Host iptables Backend
First, determine which iptables backend your host system is currently using.
sudo update-alternatives --display iptables
Expected Output Examples:
- Using
iptables-nft(modern, default on many newer distros):iptables - auto mode link currently points to /usr/sbin/iptables-nft /usr/sbin/iptables-legacy - priority 20 /usr/sbin/iptables-nft - priority 30 Current 'best' version is '/usr/sbin/iptables-nft'. - Using
iptables-legacy(older, or explicitly configured):iptables - auto mode link currently points to /usr/sbin/iptables-legacy /usr/sbin/iptables-legacy - priority 20 /usr/sbin/iptables-nft - priority 30 Current 'best' version is '/usr/sbin/iptables-legacy'.
Note the "link currently points to" or "Current 'best' version" line. Do the same for ip6tables:
sudo update-alternatives --display ip6tables
2. Verify Docker's iptables Configuration
Check how Docker is configured to interact with iptables.
docker info | grep -i 'NF tables'
Expected Output:
NFtables: true: Docker is aware ofnftablesand is likely trying to use it.NFtables: false: Docker is likely using or falling back toiptables-legacymode.
Also, inspect your Docker daemon configuration file, /etc/docker/daemon.json, for specific iptables settings.
cat /etc/docker/daemon.json
Look for settings like "iptables": false (which would prevent Docker from managing iptables rules at all, a common cause of this issue if not handled manually) or "ip-tables-mode": "legacy" / "ip-tables-mode": "nftables".
3. Align iptables Backends (Primary Resolution)
The goal is to ensure Docker and the host's active iptables backend are compatible.
Option A: Configure Docker to use nftables (Recommended for modern hosts)
If your host is using iptables-nft (via update-alternatives --display iptables), it's generally best to configure Docker to use the nftables backend directly. This option is available in newer Docker Engine versions (e.g., 20.10+).
Create or edit
/etc/docker/daemon.json:sudo vim /etc/docker/daemon.jsonAdd/Modify the following configuration:
{ "iptables": true, "ip-tables-mode": "nftables" }If
daemon.jsonalready exists, ensure you merge the new settings correctly. If"iptables": falsewas present, change it totrue. If"ip-tables-mode"was set to"legacy", change it to"nftables".
Option B: Force Host to iptables-legacy (Alternative)
If Option A is not feasible (e.g., older Docker version without ip-tables-mode setting), or if you prefer to maintain a legacy iptables environment on your host, you can switch your host's default iptables backend.
Switching the host's
iptablesbackend can impact other firewall rules or applications that rely on thenftablesbackend. Proceed with caution and understand potential side effects.
Switch the
iptablesandip6tablesalternatives:sudo update-alternatives --set iptables /usr/sbin/iptables-legacy sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacyVerify the change:
sudo update-alternatives --display iptablesConfirm it now points to
iptables-legacy.
4. Restart Docker Service
After making changes to /etc/docker/daemon.json or the host's iptables alternatives, you must restart the Docker daemon for the changes to take effect.
sudo systemctl restart docker
Restarting the Docker service will stop all running containers. Ensure this is done during a maintenance window.
5. Recreate/Restart Container
Even after restarting Docker, it's often best practice to recreate your container (or at least stop and start it) to ensure Docker re-evaluates and applies its networking rules.
If using docker run:
docker stop <container_name>
docker rm <container_name>
docker run -d -p 80:80 --name my-alpine-web alpine/nginx:1.24.0
If using docker-compose:
docker-compose down
docker-compose up -d
6. Verify net.ipv4.ip_forward
Confirm that IP forwarding is enabled on your host system. Docker usually handles this, but it's a critical prerequisite.
sysctl net.ipv4.ip_forward
If the output is net.ipv4.ip_forward = 0, enable it:
sudo sysctl -w net.ipv4.ip_forward=1
To make this change persistent across reboots, add or modify the following line in /etc/sysctl.conf:
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
7. Verify Connectivity
Finally, re-test connectivity to your container's published port.
$ curl http://localhost:80
# Expected output: HTML content from your Nginx container
$ telnet localhost 80
# Expected output: Connected to localhost. Escape character is '^]'.
And verify the host is listening:
$ sudo ss -tulpn | grep :80
# Expected output:
# tcp LISTEN 0 4096 0.0.0.0:80 0.0.0.0:* users:(("docker-proxy",pid=xxxxx,fd=4))
If you still face issues, ensure no other firewalls (like ufw or firewalld) are interfering and that the Docker DOCKER and DOCKER_FORWARD chains are correctly populated using sudo iptables -nvL and sudo iptables -nvL -t nat. However, addressing the iptables backend mismatch is usually the definitive solution for this specific problem.
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.