Troubleshooting Docker Networking: Resolving Connectivity Problems Effectively

Fix Docker networking issues with container DNS, user-defined networks, port publishing, host access, DNS, and firewalls.

Troubleshooting Docker Networking: Resolving Connectivity Problems Effectively

Docker networking problems are much easier to solve when you name the direction of the failed connection. “The container cannot connect” is too vague. Is the host trying to reach a container? Is one container trying to reach another? Is a container trying to reach the internet? Is traffic entering from another machine? Each path uses different Docker behavior.

Start by writing the path in plain language:

browser on host -> localhost:8080 -> container port 80
api container -> db container -> port 5432
worker container -> public internet -> api.example.com:443
remote laptop -> server public IP -> published container port

Once you know the path, you can test each hop instead of changing networks at random.

Know the basic Docker network modes

Most single-host Docker setups use bridge networking. Docker creates a virtual network on the host, gives containers private IP addresses, and can publish selected container ports onto the host.

The default bridge network works, but user-defined bridge networks are better for applications because they provide built-in DNS by container name. That means an api container can reach a db container at db:5432 if both are attached to the same user-defined network.

Create one like this:

docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name api --network appnet my-api

Other modes exist. host networking shares the host network namespace and removes normal port publishing behavior; it is useful in some Linux cases but reduces isolation. none gives a container no network. overlay is for multi-host Docker Swarm networking. Compose creates user-defined networks for projects automatically unless you configure otherwise.

“Network not found”

This error usually means the network name is wrong or the network exists in a different context than the command expects.

Check available networks:

docker network ls

Inspect the one you intend to use:

docker network inspect appnet

If it does not exist, create it:

docker network create appnet

With Compose, the actual network name may be prefixed by the project name. A network called backend in compose.yml may appear as myproject_backend. Use:

docker compose ps
docker network ls

If you declared an external Compose network, Compose will not create it for you:

networks:
  appnet:
    external: true

In that case, create it manually or remove external: true if Compose should manage it.

Container-to-container communication fails

For two containers to talk by name, they usually need to be on the same user-defined network. Confirm that first:

docker network inspect appnet

Look for both containers in the Containers section.

Then test from a container that has basic tools. Your application image may not include curl, dig, or ping, and that is fine. Use a temporary debug container on the same network:

docker run --rm -it --network appnet nicolaka/netshoot

From inside:

dig db
curl -v http://api:8080/health
nc -vz db 5432

If DNS fails, the containers are probably not on the same user-defined network or you are using the default bridge network expecting name resolution that it does not provide in the same way. If DNS works but connection fails, check whether the destination service is listening on the expected port.

Inside the destination container:

docker exec -it api sh
ss -ltnp || netstat -ltnp

A common mistake is binding the app to 127.0.0.1 inside the container. That only listens on loopback inside that container. Other containers cannot reach it. Configure the app to listen on 0.0.0.0.

Also make sure you use the container port, not the host-published port, for container-to-container traffic. If the database listens on 5432 in the container, other containers should use db:5432, not localhost:15432 or the published host port.

Host cannot reach a container

For the host to reach a service in a bridge-networked container, you normally need a published port:

docker run -d --name web -p 8080:80 nginx

This maps host port 8080 to container port 80. Test from the host:

curl -v http://localhost:8080

Check what Docker published:

docker port web
docker ps --format 'table {{.Names}}	{{.Ports}}'

If there is no port mapping, EXPOSE in the Dockerfile does not publish the port. EXPOSE is documentation and metadata. You still need -p or Compose ports:.

If the port is published but connection fails, check four things:

  1. The application is listening inside the container on the container port.
  2. The application is listening on 0.0.0.0, not only 127.0.0.1.
  3. No host firewall blocks the host port.
  4. No other process already owns the host port.

Find host port conflicts:

sudo lsof -i :8080
# or
sudo ss -ltnp 'sport = :8080'

For Compose, remember the syntax:

ports:
  - "8080:80"

The left side is the host port. The right side is the container port.

Container cannot reach the internet

Test IP connectivity and DNS separately:

docker exec -it app sh
ping -c 2 1.1.1.1
ping -c 2 example.com

Some images do not include ping. Use curl if available:

curl -I https://example.com

If IP works but names fail, it is a DNS problem. Check:

cat /etc/resolv.conf

Docker normally injects resolver settings. Corporate VPNs, custom DNS, and Docker Desktop networking can complicate this. You can configure daemon-level DNS in Docker's daemon settings, or pass DNS for a specific container:

docker run --dns 1.1.1.1 ...

Do not use public DNS blindly in corporate environments where internal names must resolve. Use the DNS servers appropriate for the network.

If neither IP nor DNS works, check whether the container is on --network none, whether host firewall/NAT rules are broken, whether the Docker daemon has custom networking settings, and whether the host itself has internet access.

A container needs to reach a service on the host

From a container, localhost means the container itself, not the host. This is one of the most common Docker networking surprises.

On Docker Desktop, host.docker.internal usually resolves to the host. On modern Docker Engine for Linux, you can add a host gateway entry:

docker run --add-host=host.docker.internal:host-gateway ...

Then the container can call:

curl http://host.docker.internal:3000

Make sure the host service listens on an address reachable from Docker, not only a loopback binding that Docker cannot access in your environment. If a local development server only binds to 127.0.0.1, you may need to bind it to 0.0.0.0 or the host interface, depending on OS and security requirements.

Remote machines cannot reach the container

If the host can reach localhost:8080 but another machine cannot reach server-ip:8080, Docker may be fine. Check the host firewall, cloud security group, router/NAT, and whether Docker published only to loopback.

This publishes to all host interfaces:

docker run -p 8080:80 nginx

This publishes only to localhost:

docker run -p 127.0.0.1:8080:80 nginx

Loopback-only publishing is often desirable for local development or reverse-proxy setups, but it will block remote access by design.

On cloud servers, also check provider firewalls. Opening ufw on the VM does not help if the cloud security group still blocks the port.

Compose networking mistakes

Compose gives each service a DNS name based on the service name. If your service is called db, other services should usually connect to db, not localhost.

Example:

services:
  api:
    build: .
    environment:
      DATABASE_URL: postgres://postgres:postgres@db:5432/app
    depends_on:
      - db
  db:
    image: postgres:16

depends_on controls startup order, not readiness. The database container may start before Postgres is ready to accept connections. Your application should retry connections or use a healthcheck-aware startup pattern.

Also distinguish ports from expose. ports publishes to the host. expose documents or exposes ports to linked services but does not make them reachable from the host in the same way.

Packet-level debugging

When normal checks do not explain the problem, use a network debug image:

docker run --rm -it --network container:<target-container> nicolaka/netshoot

That joins the target container's network namespace, which lets you inspect networking from the same point of view without installing tools in the app image.

Useful commands include:

ip addr
ip route
cat /etc/resolv.conf
dig service-name
curl -v http://service-name:port
tcpdump -nn -i any port 8080

Use tcpdump when you need to know whether packets arrive at all. If packets never arrive, look before the container: publishing, firewall, routing, load balancer. If packets arrive and no response leaves, look inside the container or application.

A short troubleshooting flow

Use this order for most Docker networking issues:

  1. Define the exact path: host to container, container to container, container to internet, or remote to host to container.
  2. Check network attachment with docker network inspect.
  3. Check name resolution from the source side.
  4. Check whether the destination process is listening on the right interface and port.
  5. Check port publishing only for traffic that crosses from host to container.
  6. Check firewall, VPN, proxy, DNS, and cloud security rules outside Docker.

Most Docker networking problems are not deep Docker bugs. They are usually wrong names, wrong ports, loopback binding, missing published ports, DNS assumptions, or traffic being tested from the wrong side of the boundary.