Fix Docker Networking Issues in 12 Minutes

Solve container connection failures, DNS problems, and port conflicts with step-by-step Docker network debugging.

Problem: Containers Can't Talk to Each Other

Your Docker containers work individually but can't communicate. You see connection refused, DNS resolution fails, or services timeout when calling each other.

You'll learn:

  • Why Docker networking breaks between containers
  • How to diagnose network connectivity issues
  • When to use bridge vs custom networks

Time: 12 min | Level: Intermediate


Why This Happens

Docker's default bridge network doesn't provide automatic DNS resolution between containers. When you reference containers by name, Docker can't resolve them unless they're on a custom network.

Common symptoms:

  • curl: (6) Could not resolve host: api-container
  • connection refused when using container names
  • Works with IP addresses but not names
  • Port mappings conflict between containers

Solution

Step 1: Verify Current Network Setup

# List all networks
docker network ls

# Inspect your containers' networks
docker inspect <container-name> | grep NetworkMode

Expected: You'll likely see "NetworkMode": "default" or "NetworkMode": "bridge"

Why this matters: The default bridge doesn't support container name DNS resolution.


Step 2: Check Container Connectivity

# Get container IP addresses
docker inspect -f '{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -q)

# Test connection from one container to another
docker exec <source-container> ping <target-container>

If ping fails with "Name or service not known": DNS resolution is broken (expected on default bridge).

If ping fails with "Destination Host Unreachable": Network isolation issue.


Step 3: Create a Custom Network

# Create a user-defined bridge network
docker network create app-network

# Connect existing containers
docker network connect app-network <container-name>

# OR restart containers on the new network
docker run -d --name api --network app-network your-api-image
docker run -d --name web --network app-network your-web-image

Why this works: Custom networks provide automatic DNS resolution. Containers can now reach each other using their names.


Step 4: Update Service References

# docker-compose.yml
version: '3.8'

services:
  api:
    image: your-api
    networks:
      - app-network
    ports:
      - "3000:3000"
  
  web:
    image: your-web
    environment:
      # Use service name, not localhost
      API_URL: http://api:3000
    networks:
      - app-network
    depends_on:
      - api

networks:
  app-network:
    driver: bridge

Critical change: Use service names (api:3000) not localhost:3000 when containers communicate.


Step 5: Handle Port Conflicts

# Find what's using a port
sudo lsof -i :3000
# or on Linux
sudo netstat -tulpn | grep 3000

# Change host port mapping (keep container port same)
docker run -p 3001:3000 your-image  # Host:3001 → Container:3000

If services still can't connect:

  • Error: "bind: address already in use" → Change the host port (first number in -p)
  • Connection refused inside containers → Don't use published ports internally, use container ports directly

Verification

# Test DNS resolution between containers
docker exec web ping api

# Test HTTP connectivity
docker exec web curl http://api:3000/health

# Check network members
docker network inspect app-network

You should see:

  • Successful pings between containers
  • HTTP responses without connection errors
  • All your services listed in network members

Common Troubleshooting Patterns

Pattern 1: "Connection Refused" on localhost

# ⌠Wrong: Using localhost inside containers
curl http://localhost:3000

# ✅ Right: Using service name
curl http://api:3000

Why: localhost inside a container refers to that container, not the host or other containers.


Pattern 2: Works in Development, Breaks in Production

# ⌠Wrong: Hardcoded localhost
environment:
  DATABASE_URL: postgresql://localhost:5432/db

# ✅ Right: Service name
environment:
  DATABASE_URL: postgresql://postgres:5432/db

Why: Production containers are isolated. Use service names for portability.


Pattern 3: Port Conflicts on Host

# ⌠Wrong: Same host port for multiple services
services:
  api-v1:
    ports: ["3000:3000"]
  api-v2:
    ports: ["3000:3000"]  # Conflict!

# ✅ Right: Different host ports
services:
  api-v1:
    ports: ["3001:3000"]
  api-v2:
    ports: ["3002:3000"]

Inter-container communication: Containers on the same network still use port 3000 internally.


Advanced Debugging

Check Network Traffic

# Install network tools in container
docker exec -it <container> sh
apk add --no-cache tcpdump curl bind-tools

# Test DNS resolution
nslookup api

# Trace route to another container
traceroute api

# Capture packets
tcpdump -i eth0 port 3000

Inspect Network Isolation

# See which networks a container is on
docker inspect <container> -f '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}'

# Check network driver
docker network inspect app-network -f '{{.Driver}}'

If containers are on different networks: They can't communicate unless you connect both to a shared network.


What You Learned

  • Default bridge networks don't support DNS between containers
  • Custom networks provide automatic service discovery
  • Use service names, not localhost, for inter-container communication
  • Host port mappings are for external access, not internal communication

Limitations:

  • This applies to bridge networks. Host network mode bypasses isolation entirely
  • Swarm mode and Kubernetes have different networking models

Quick Reference

# Create network
docker network create my-network

# Run container on network
docker run --network my-network --name api my-image

# Connect existing container
docker network connect my-network existing-container

# Debug DNS
docker exec container nslookup other-container

# Check connectivity
docker exec container curl http://other-container:port

Tested on Docker 25.x, Docker Compose v2.24, Ubuntu 24.04 & macOS Sonoma