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:
| Cause | Symptom | How to Verify |
|---|---|---|
| Different networks | Connection timeout or no route | docker network inspect |
| Default bridge network (no DNS) | ENOTFOUND by service name | Check network type |
| Target container not ready | Connection refused | docker compose ps — is target healthy? |
| Wrong port or hostname | Connection refused | Compare ports and service names |
| Host-mode networking conflict | Can reach by IP but not hostname | Check network_mode |
| Firewall/iptables rules | Connection timed out | iptables -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).
Related
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments