Docker Containers Can't Communicate: Fixes

The Problem

One Docker container can’t reach another. Typical symptoms:

Connection refused
Could not connect to host "db" port 5432: Connection refused
dial tcp 172.18.0.2:3306: connect: connection refused
getaddrinfo ENOTFOUND redis
No route to host
Connection timed out

Your app container can’t reach its database, cache, or another service — even though both containers are running.

The Cause

Docker network isolation is working as designed — containers can only communicate when they share a network. The most common causes of inter-container connectivity failures:

CauseSymptomHow to Verify
Different networksConnection timeout or no routedocker network inspect
Default bridge network (no DNS)ENOTFOUND by service nameCheck network type
Target container not readyConnection refuseddocker compose ps — is target healthy?
Wrong port or hostnameConnection refusedCompare ports and service names
Host-mode networking conflictCan reach by IP but not hostnameCheck network_mode
Firewall/iptables rulesConnection timed outiptables -L -n

The Fix

Method 1: Put Containers on the Same Network

The most common fix. Containers in the same docker-compose.yml share a default network, but containers in separate Compose stacks don’t.

Same Compose file — explicit network (recommended):

services:
  app:
    image: myapp:latest
    networks:
      - backend

  db:
    image: postgres:17-alpine
    networks:
      - backend

networks:
  backend:
    driver: bridge

Cross-stack communication — shared external network:

Create the network once:

docker network create shared

In each Compose file, reference it:

# Stack A (app)
services:
  app:
    networks:
      - shared

networks:
  shared:
    external: true
# Stack B (database)
services:
  db:
    networks:
      - shared

networks:
  shared:
    external: true

Now app can reach db by hostname across Compose stacks.

Method 2: Fix Service Name Resolution

Containers resolve each other by service name (from docker-compose.yml), not by container name.

services:
  app:
    environment:
      DATABASE_HOST: db  # Use service name, not container_name
  db:
    container_name: my-postgres  # This is NOT the DNS name

The hostname is db, not my-postgres. If you set container_name, other containers still use the service name for DNS resolution.

Exception: On the default bridge network, Docker DNS doesn’t work. Containers can only communicate by IP address. Always use custom networks.

Method 3: Fix Port Mismatch

A container exposes a port internally, but you’re connecting to the wrong one.

services:
  db:
    image: postgres:17-alpine
    ports:
      - "5433:5432"  # Host port 5433 → Container port 5432
  • From another container: connect to db:5432 (internal port)
  • From the host: connect to localhost:5433 (mapped port)

Common mistake: Using the host-mapped port from inside another container. Containers communicate on internal ports, not host-mapped ports.

Method 4: Wait for Target Container to Be Ready

“Connection refused” often means the target service hasn’t finished starting.

services:
  app:
    depends_on:
      db:
        condition: service_healthy  # Wait for health check to pass

  db:
    image: postgres:17-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s

Without condition: service_healthy, depends_on only waits for the container to start — not for the service inside it to be ready.

Method 5: Fix host Network Mode Conflicts

If a container uses network_mode: host, it bypasses Docker networking entirely:

services:
  app:
    network_mode: host  # This container is NOT on any Docker network

This container can reach the host’s ports directly but cannot use Docker DNS to find other containers by name. Other containers also can’t reach it by service name.

Fix: Remove network_mode: host and use normal port mapping. Only use host networking when the application genuinely needs it (e.g., network scanning, DHCP serving).

Method 6: Fix Firewall/iptables Rules

Docker modifies iptables rules to enable container networking. If you’ve configured UFW or custom iptables rules, they may conflict.

# Check if Docker's iptables rules are present
sudo iptables -L DOCKER -n

# If empty or missing, Docker networking may be broken
# Restart Docker to re-create rules
sudo systemctl restart docker

UFW conflict: If you enabled UFW after Docker, UFW’s default deny rules may block Docker bridge traffic:

# Allow traffic on Docker bridge interfaces
sudo ufw allow in on docker0
sudo ufw allow in on br-+  # Allow all Docker bridge networks

Or add to /etc/ufw/after.rules (before the final COMMIT):

-A ufw-before-forward -i docker0 -o docker0 -j ACCEPT
-A ufw-before-forward -i br-+ -o br-+ -j ACCEPT

Diagnostic Commands

# List all networks and which containers are on them
docker network ls
docker network inspect <network_name>

# Test connectivity from inside a container
docker exec -it myapp ping db
docker exec -it myapp nslookup db
docker exec -it myapp curl -v http://db:5432

# Check which ports a container is listening on
docker exec -it db ss -tlnp

# Check if container is reachable from another
docker exec -it myapp wget -qO- http://db:8080/health

# Inspect container's DNS configuration
docker exec -it myapp cat /etc/resolv.conf

Prevention

1. Always Define Networks Explicitly

Don’t rely on Docker Compose’s implicit default network — name it:

networks:
  backend:
    driver: bridge

2. Use Health Checks and depends_on Conditions

Every service that other containers depend on should have a health check. Use condition: service_healthy in depends_on.

3. Connect to Internal Ports

When one container connects to another, use the internal container port — not the host-mapped port.

4. Use Service Names for DNS

Always use the Docker Compose service name (e.g., db, redis, app) for inter-container communication, never localhost or 127.0.0.1 (those refer to the container itself).

Comments