Why Your Containers Can't Talk to Each Other
by Eric Hanson, Backend Developer at Clean Systems Consulting
The connection error you can't reproduce on your machine
Your Docker Compose stack has an app service and a database service. Locally, it works fine. In CI, the app consistently fails to connect to the database on the first attempt. Or: you have two separate Docker Compose projects and you've connected them to the same network, but service A still can't reach service B. Or: your service container connects to the database by IP address and it works until you restart the database container, which gets a new IP.
These are the four categories of inter-container communication failures:
- Wrong hostname
- Wrong or mismatched network
- Service binding to the wrong interface
- IP address hardcoding
Each has a specific diagnosis path.
Category 1: wrong hostname
Inside a Docker network, containers are reachable by their container name or (in Compose) their service name. If your application's connection string references localhost, 127.0.0.1, or the host machine's IP, it won't reach another container.
Diagnosis:
# From inside the failing container, test DNS resolution
docker exec -it your-app-container nslookup postgres
docker exec -it your-app-container ping -c 3 postgres
If nslookup postgres returns the database container's IP, DNS works. If it fails with "Name or service not known," the containers aren't on the same network, or the service name doesn't match.
Fix: update the connection URL in your application config to use the service name:
# Wrong
DATABASE_URL=jdbc:postgresql://localhost:5432/mydb
DATABASE_URL=jdbc:postgresql://127.0.0.1:5432/mydb
# Right — 'postgres' is the Compose service name or container name
DATABASE_URL=jdbc:postgresql://postgres:5432/mydb
Category 2: containers on different networks
Docker containers on different networks cannot communicate by default. This is a common issue when:
- You have multiple Compose projects and want services from one to reach services from another
- You started containers with
docker runwithout specifying a network, so they landed on the defaultbridgenetwork
Diagnosis:
# List all networks
docker network ls
# Inspect which containers are on which network
docker network inspect your-network-name
# Check what networks a specific container is on
docker inspect your-container-name | jq '.[0].NetworkSettings.Networks | keys'
Fix for cross-project Compose communication: create an external network and attach both projects to it.
docker network create shared-network
In each docker-compose.yml:
networks:
shared-network:
external: true
services:
service-a:
networks:
- shared-network
- default # still on the default project network
With external: true, Compose connects the service to the pre-existing shared-network without trying to create or manage it. Services from both projects on shared-network can reach each other by container name.
Note: in cross-project scenarios, the DNS name is the container name (which Compose generates as {project-name}-{service-name}-1), not just the service name. This is a common gotcha.
# Find the actual container name
docker ps --format "table {{.Names}}"
# Example: myproject-app-1, myproject-db-1
Category 3: service binding to the wrong interface
A service inside a container listens on a network interface. If it listens on 127.0.0.1 (loopback only), it's not reachable from other containers even on the same Docker network. The service must listen on 0.0.0.0 to be reachable from outside the container.
This is a common issue with:
- Go HTTP servers that default to
localhost:portinstead of:port - Applications configured with explicit bind addresses for security reasons
- Some databases configured to listen on localhost only
Diagnosis from another container:
# Telnet/nc to test connectivity (if nc is available)
docker exec -it app-container nc -zv database-service 5432
# If you can get a shell in the database container:
docker exec -it db-container ss -tlnp | grep 5432
# Look for '0.0.0.0:5432' vs '127.0.0.1:5432'
If the service shows 127.0.0.1:5432 rather than 0.0.0.0:5432, it's only reachable from within that container.
Fix: configure the service to listen on all interfaces. For PostgreSQL:
services:
db:
command: postgres -c listen_addresses='*'
For your own Go/Java/Python service: change the bind address from localhost to 0.0.0.0 or just use the port without a hostname:
// Go — wrong
http.ListenAndServe("localhost:8080", handler)
// Go — right: listens on all interfaces
http.ListenAndServe(":8080", handler)
http.ListenAndServe("0.0.0.0:8080", handler)
Category 4: hardcoded IP addresses
Container IP addresses change when containers are recreated. If your application connects to another service using a hardcoded IP (from docker inspect), it works until anything is restarted.
# This IP is temporary — don't hardcode it
docker inspect db-container | grep IPAddress
# "IPAddress": "172.18.0.2"
Fix: always use service names or container names, never IPs. Docker's internal DNS handles resolution. If you need the IP programmatically for some reason, re-resolve it at runtime rather than storing it.
Systematic diagnosis workflow
When inter-container communication fails, work through this sequence:
# 1. Confirm both containers are running
docker ps | grep -E "container-a|container-b"
# 2. Confirm they're on the same network
docker network inspect $(docker network ls -q) | grep -A5 '"Name"'
# 3. Test DNS from inside the failing container
docker exec -it failing-container nslookup target-service
# 4. Test TCP connectivity
docker exec -it failing-container timeout 5 bash -c 'echo > /dev/tcp/target-service/8080' \
&& echo "connected" || echo "failed"
# 5. Check the target service's bind address
docker exec -it target-container ss -tlnp
If step 3 fails: networks are mismatched or service names don't match. If step 3 succeeds but step 4 fails: the service is bound to loopback only. If both succeed: the problem is in the application (auth, protocol mismatch, TLS).
The fast path for Compose setups
In Compose, networking is supposed to be automatic. If it's not working:
- Are both services in the same
docker-compose.yml? Same network by default → service names resolve. - Are services in different Compose files/projects? Different networks by default → need an external network.
- Is the
ports:mapping present but the application still can't connect internally?ports:is for host access, not required for container-to-container — remove it from the diagnosis path. - Did you use
network_mode: hoston one service? That service is on the host network, not the Compose network.
docker compose config shows the fully resolved configuration including network assignments — check it when the Compose setup doesn't behave as expected.