Containers Advanced

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.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

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.

  1. iptables-legacy vs. nftables Backend Mismatch:

    • Historically, Linux systems used iptables (now referred to as iptables-legacy). Modern distributions, including Alpine Linux internally and many host systems (e.g., Debian 10+, Ubuntu 20.04+), have transitioned to nftables as the default packet filtering framework.
    • The iptables command-line utility on these newer systems often acts as a frontend for nftables (iptables-nft), translating iptables syntax into nftables rules.
    • Docker, depending on its version and the host's configuration, might still prefer or operate in iptables-legacy mode when creating its DOCKER and DOCKER_FORWARD chains and DNAT rules for port mappings.
    • If the host's iptables is configured to use the nftables backend, but Docker is attempting to insert iptables-legacy rules (or vice-versa, or if the translation layer is buggy), the necessary PREROUTING and OUTPUT chain 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 incompatible iptables backend.
  2. net.ipv4.ip_forward Disabled:

    • 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.
  3. Conflicting Firewall Rules (Less Common for this specific symptom):

    • While firewalld or ufw on the host can interfere, they usually block all traffic to Docker containers unless explicitly configured. The specific symptom of docker ps showing the port mapping but no host listener often points more directly to an iptables backend issue rather than a high-level firewall blocking all traffic.

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 of nftables and is likely trying to use it.
  • NFtables: false: Docker is likely using or falling back to iptables-legacy mode.

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+).

  1. Create or edit /etc/docker/daemon.json:

    sudo vim /etc/docker/daemon.json
    
  2. Add/Modify the following configuration:

    {
      "iptables": true,
      "ip-tables-mode": "nftables"
    }
    

    If daemon.json already exists, ensure you merge the new settings correctly. If "iptables": false was present, change it to true. 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 iptables backend can impact other firewall rules or applications that rely on the nftables backend. Proceed with caution and understand potential side effects.

  1. Switch the iptables and ip6tables alternatives:

    sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
    sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
    
  2. Verify the change:

    sudo update-alternatives --display iptables
    

    Confirm 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.

👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

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.