Control panels

Troubleshooting container networking issues: bridge vs host mode in Docker

Bridge and host mode move packets in different ways, so the same symptom usually has a different cause. Work through host, container and DNS checks to find the real fault.

IntermediateLinux server7 min read

When a container cannot be reached from the internet, or cannot reach anything itself, the first question is which network mode it uses. In bridge mode the container sits behind Docker's NAT and you publish ports with -p; in host mode the container shares the host's network stack directly and -p does nothing. Knowing which one you are debugging tells you where to look.

This guide walks through the checks in order, from the container outward, so you can find whether the problem is the application bind address, the published port, the host firewall, DNS, or MTU.

Before you start

You need:

  • A Linux VPS with root access and Docker installed. If Docker is not on the server yet, see Install Docker and Docker Compose on a Linux VPS.
  • SSH access, or the browser console in your VPS provider's control panel if SSH is not answering.
  • A container to test with. Any web image works; examples below use a container named web publishing port 8080 on the host.
  • The server's public IP. Examples use 203.0.113.10 — replace it with yours.

Note: Run the host commands as root or with sudo. The docker exec commands run inside the container and do not need sudo.

Understand what each mode does

Bridge mode is the default. Docker creates a docker0 bridge, gives the container an address on it (usually in 172.17.0.0/16), and adds NAT rules so the container can reach the internet. Incoming traffic only arrives if you publish a port with -p 8080:80, which adds a DNAT rule on the host.

Host mode removes that layer. With --network host the container binds directly to the host's interfaces and ports. There is no NAT, no separate IP, and -p is ignored with a warning. That makes host mode fast and simple for things like a reverse proxy, but every port conflict is now a conflict with the host itself.

Step 1: Confirm the network mode and addresses

Start by listing what is actually running and how.

Bash
docker ps --format 'table {{.Names}}\t{{.Ports}}\t{{.Status}}'
docker inspect -f '{{.Name}} -> {{.HostConfig.NetworkMode}}' $(docker ps -q)
docker network ls

The Ports column shows the mapping for bridge containers (0.0.0.0:8080->80/tcp) and is empty for host mode. docker inspect confirms the mode. If a container reports bridge but you expected host, the mode flag is missing from the run command or compose file.

Look at the bridge itself:

Bash
ip -brief addr show docker0
ip route

You should see an address such as 172.17.0.1/16 on docker0. If the interface does not exist, no bridge-mode container has started yet.

Step 2: Test from inside the container

Get a shell inside the container and check the three things that break most often: address, route and DNS.

Bash
docker exec -it web sh -c 'ip addr; echo ---; ip route; echo ---; cat /etc/resolv.conf'

Then test outbound traffic:

Bash
docker exec -it web ping -c 3 1.1.1.1
docker exec -it web curl -sS -o /dev/null -w '%{http_code}\n' https://example.com

If the ping to an IP works but the name lookup fails, the problem is DNS, not routing. If both fail, the container has no working path out. See Open a shell inside a running Docker container for more detail on the exec flags.

Step 3: Test the published port from the host, then from outside

For a bridge container, check that the host is listening and that the rule works locally.

Bash
ss -lntp | grep 8080
curl -v http://127.0.0.1:8080
curl -v http://203.0.113.10:8080

Three outcomes matter:

  • Localhost works, public IP fails on the same machine → a host firewall is dropping the port. Compare rule sets with UFW vs firewalld vs nftables vs iptables and open the port you published.
  • Both fail with connection refused → nothing is listening. Check the container is running, the port mapping exists, and the application inside binds 0.0.0.0, not 127.0.0.1.
  • Localhost fails but the container is up → the published port is bound to a specific host address, for example -p 127.0.0.1:8080:80, which is only reachable from the server itself.

Test from a different machine, not from the server, because traffic from the host to its own public IP takes a different path:

Bash
curl -v http://203.0.113.10:8080

Step 4: Fix host mode port conflicts

Host mode fails differently. There is no NAT, so the container's process must be able to bind the port on the host.

Bash
ss -lntp | grep -E ':(80|443|8080)\b'
docker logs --tail 50 web

If another process already owns the port, the container exits or logs address already in use. Stop the conflicting service, change the port the container listens on, or move that container back to bridge mode with a published port. Remember that -p in a host-mode run is silently ignored — do not debug a mapping that was never created.

Step 5: Container-to-container DNS

Containers on the default bridge can reach each other by IP but not by name. Name resolution between containers works only on a user-defined network, where Docker runs an embedded DNS server at 127.0.0.11.

Bash
docker network create appnet
docker run -d --name web --network appnet nginx
docker run --rm --network appnet alpine getent hosts web

If the last command returns nothing, the containers are not on the same user-defined network. Create one and attach both, as described in Docker networks: bridge, host and custom networks explained.

Step 6: Check outbound traffic and MTU

When containers can reach the gateway but not the internet, check forwarding and NAT rules on the host:

Bash
sysctl net.ipv4.ip_forward
iptables -t nat -S POSTROUTING | head

net.ipv4.ip_forward must be 1. If it is 0, enable it in /etc/sysctl.d/99-docker.conf with net.ipv4.ip_forward=1 and apply with sysctl --system. Docker normally sets this, so a 0 here means something else changed it.

A second common cause is an MTU mismatch on networks that use tunnels or encapsulation. Small requests work and larger ones hang. Test it:

Bash
docker run --rm alpine ping -M do -s 1400 -c 2 1.1.1.1

If that fails, lower the MTU for the Docker daemon in /etc/docker/daemon.json:

JSON
{
  "mtu": 1400
}

Warning: Restarting Docker stops and restarts every running container unless you enabled live restore. Plan for a short outage before you run systemctl restart docker.

Then restart the daemon and re-run the test.

Step 7: Reach the host from a container

If a container needs to call a service on the host itself, do not hard-code the host IP. Add a name that points at the host gateway:

Bash
docker run --rm --add-host=host.docker.internal:host-gateway alpine getent hosts host.docker.internal

Check it works

Run these in order and all four should pass:

Bash
docker inspect -f '{{.HostConfig.NetworkMode}}' web
docker exec -it web curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1
curl -sS -o /dev/null -w '%{http_code}\n' http://203.0.113.10:8080
docker exec -it web getent hosts example.com

The first confirms the mode, the second proves the application answers inside the container, the third proves the path from the internet, and the fourth proves DNS. A 000 code means curl never connected — go back to the step that covers that layer.

Troubleshooting

  • Connection refused from outside, works on localhost. The application binds 127.0.0.1 inside the container. Change its listen address to 0.0.0.0 and restart the container.
  • port is already allocated. Another container or host process owns that host port. Find it with ss -lntp and pick a different published port.
  • Containers cannot resolve each other by name. They are on the default bridge, which has no DNS. Create a user-defined network and attach both containers.
  • Ping to an IP works, hostnames fail. Check /etc/resolv.conf inside the container and the host's own resolver. A broken host resolver propagates into containers.
  • Small requests succeed, large transfers stall. Classic MTU problem. Lower the daemon MTU as shown above and restart Docker.
  • -p seems to have no effect. The container runs in host mode. Remove --network host or stop passing -p.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.