Fix: Docker Compose Services Can't Connect to Each Other
Quick Answer
How to fix Docker Compose networking issues — services can't reach each other by hostname, port mapping confusion, network aliases, depends_on timing, and host vs container port differences.
The Error
One Docker Compose service can’t connect to another:
Error: connect ECONNREFUSED 127.0.0.1:5432
Error: getaddrinfo ENOTFOUND postgres
connect: connection refused [::]:6379Or the connection works for external access but not between services:
# You can reach http://localhost:3000 from your browser
# But from inside the app container, http://localhost:8080 failsOr after adding a service to docker-compose.yml, it can’t be reached by the other services.
Why This Happens
Docker Compose creates a private network for all services in a docker-compose.yml file. Services communicate using their service name as the hostname, not localhost or the host IP. Common causes of connection failures:
- Using
localhostinstead of the service name — inside a container,localhostrefers to that container itself, not other containers. Usedbnotlocalhost:5432. - Port mapping confusion —
ports: "5432:5432"exposes the container port to the host. Between containers on the same Compose network, use the container port directly without mapping. - Service not on the same network — services defined in different Compose files or using
network_mode: hostmay not be on the same Docker network. - Service not ready when another tries to connect —
depends_ononly waits for the container to start, not for the application inside to be ready. - Custom networks not shared — if you define custom networks and not all services are on the same one, they can’t reach each other.
- DNS resolution failure — Docker’s internal DNS resolves service names, but misconfigured networking can break this.
Fix 1: Use the Service Name as the Hostname
The service name in docker-compose.yml is the hostname other services use to connect:
services:
app:
image: myapp
environment:
# WRONG — localhost is the app container itself
DATABASE_URL: postgres://user:pass@localhost:5432/mydb
# CORRECT — use the service name
DATABASE_URL: postgres://user:pass@db:5432/mydb
# CORRECT — use the service name for Redis too
REDIS_URL: redis://cache:6379
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
cache:
image: redis:7-alpineThe hostname db resolves to the db container’s IP address on Docker’s internal network. The hostname cache resolves to the cache container.
Common Mistake: Developers hardcode
localhostin their app configuration because it works locally without Docker. When running in Docker Compose,localhostinside theappcontainer refers to theappcontainer’s own network interface — not thedbcontainer.
Fix 2: Understand Port Mapping vs Container Ports
ports in Compose maps container ports to host ports — for access from outside Docker (your browser, curl). Between services on the same Compose network, use the container port directly:
services:
db:
image: postgres:16
ports:
- "5433:5432" # Maps host:5433 → container:5432
# From your HOST machine: psql -h localhost -p 5433
# From OTHER CONTAINERS: postgres://db:5432 ← container port, not 5433services:
app:
environment:
# WRONG — 5433 is the host port, not the container port
DATABASE_URL: postgres://user:pass@db:5433/mydb
# CORRECT — use the container port (5432 is what PostgreSQL listens on inside the container)
DATABASE_URL: postgres://user:pass@db:5432/mydbThe port mapping only matters for host-to-container access. Container-to-container access always uses the container’s internal port.
Fix 3: Wait for Service Readiness
depends_on ensures the container starts before dependent services, but it doesn’t wait for the application inside to be ready:
# This starts db before app, but app might try to connect before Postgres is ready
services:
app:
depends_on:
- db # Waits for container start, not for Postgres to accept connections
db:
image: postgres:16Use health checks for proper readiness waiting:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s # Give Postgres time to initialize before health checks start
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 3
app:
image: myapp
depends_on:
db:
condition: service_healthy # Waits until db health check passes
cache:
condition: service_healthy
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/mydb
REDIS_URL: redis://cache:6379condition: service_healthy only works if the dependency defines a healthcheck. Without it, Compose defaults to condition: service_started (container started, not app ready).
Fix 4: Verify the Network Configuration
By default, Compose creates one network for all services in the file and puts all services on it. Check the network setup:
# List networks
docker network ls
# Inspect which containers are on a network
docker network inspect <project_name>_default
# Example: if your project dir is "myapp", the network is "myapp_default"
docker network inspect myapp_defaultIf you define custom networks, include all relevant services:
services:
app:
networks:
- frontend
- backend # app is on both networks
db:
networks:
- backend # db is only on backend
nginx:
networks:
- frontend # nginx is only on frontend
networks:
frontend:
backend:If app is on frontend only and db is on backend only, they can’t reach each other. Both must share a network.
Confirm services can reach each other by running a test command:
# From inside the app container, ping the db container
docker compose exec app ping db
# Or test the actual port
docker compose exec app curl -s http://api:3000/health
docker compose exec app nc -zv db 5432Fix 5: Fix host.docker.internal for Host Machine Access
Sometimes you need a container to reach a service running on your host machine (not in Docker). Don’t use localhost — use host.docker.internal:
services:
app:
environment:
# Reach a service running on the host machine
EXTERNAL_API_URL: http://host.docker.internal:8080host.docker.internal is available on Docker Desktop (Mac and Windows). On Linux, add it manually:
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway" # Linux only
environment:
EXTERNAL_API_URL: http://host.docker.internal:8080Fix 6: Connect to an External Docker Network
When containers are started by different Compose files (e.g., a shared infrastructure Compose and a per-project Compose), they’re on different networks by default and can’t reach each other.
Solution — use an external network:
# Create a shared network
docker network create shared_network# infrastructure/docker-compose.yml
services:
db:
image: postgres:16
networks:
- shared_network
networks:
shared_network:
external: true # Use the pre-created network# myapp/docker-compose.yml
services:
app:
image: myapp
environment:
DATABASE_URL: postgres://user:pass@db:5432/mydb
networks:
- shared_network
networks:
shared_network:
external: true # Same pre-created networkBoth app and db are on shared_network, so app can resolve db as a hostname.
Fix 7: Debug DNS Resolution Inside a Container
If you’re unsure whether DNS resolution is working, run commands inside the container:
# Test DNS resolution
docker compose exec app nslookup db
docker compose exec app getent hosts db
# Test connectivity
docker compose exec app curl -v http://db:5432
docker compose exec app ping -c 3 db
# Check environment variables (confirm DATABASE_URL uses the right hostname)
docker compose exec app env | grep DATABASE
docker compose exec app env | grep REDISInspect the container’s network configuration:
docker inspect <container_name> | grep -A 20 '"Networks"'This shows the container’s IP address and which networks it’s connected to.
Still Not Working?
Check for network mode conflicts. If a service uses network_mode: host, it shares the host’s network stack and is not on the Compose network. It can’t be reached by service name from other containers:
# This service is NOT on the Compose network
service_a:
network_mode: host # Bypasses Docker networking
# service_b cannot connect to service_a using hostname
service_b:
environment:
URL: http://service_a:3000 # Fails — service_a is not DNS-resolvableIf you need network_mode: host for one service, the other services must use the host’s IP (host.docker.internal on Mac/Windows, or the host’s actual IP on Linux).
Check for conflicting port bindings. If two services both try to bind to the same host port, the second one fails to start:
docker compose logs service_b | grep "address already in use"Recreate the network. Sometimes Docker networks get into a bad state. Stop everything, remove the network, and start fresh:
docker compose down -v # Stop and remove containers and volumes
docker network prune # Remove unused networks
docker compose up -d # Recreate everythingFor related Docker issues, see Fix: Docker Container Keeps Restarting and Fix: Docker Compose Env Not Loaded.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Docker Secrets Not Working — BuildKit --secret Not Mounting, Compose Secrets Undefined, or Secret Leaking into Image
How to fix Docker secrets — BuildKit secret mounts in Dockerfile, docker-compose secrets config, runtime vs build-time secrets, environment variable alternatives, and verifying secrets don't leak into image layers.
Fix: Docker Compose Healthcheck Not Working — depends_on Not Waiting or Always Unhealthy
How to fix Docker Compose healthcheck issues — depends_on condition service_healthy, healthcheck command syntax, start_period, custom health scripts, and debugging unhealthy containers.
Fix: docker-compose.override.yml Not Working — Override File Ignored or Not Merged
How to fix docker-compose.override.yml not being applied — file naming, merge behavior, explicit file flags, environment-specific configs, and common override pitfalls.
Fix: Docker Build ARG Not Available — ENV Variables Missing at Runtime
How to fix Docker ARG and ENV variable issues — build-time vs runtime scope, ARG before FROM, multi-stage build variable passing, secret handling, and .env file patterns.