Docker Compose "Connection Refused": It's (almost) Always localhost
⏱️ 2 min read
The error is connection refused inside a compose stack; the cause is usually one sentence: inside a container, localhost means that container, not the host, not the other service. Each compose service gets its own network namespace — the database listening on localhost:5432 inside its container is unreachable from yours via localhost.
The fix
Connect by service name: postgres:5432, redis:6379. Compose's embedded DNS resolves service names to container IPs on the shared network. Your app config probably reads DB_HOST=localhost from a .env file written for bare-metal — change it to the service name and it just works.
The three follow-up gotchas
"But it works from my laptop" — yes: you connect via the published port (ports: "5432:5432"), which forwards from the host. The container-to-container path doesn't use published ports at all; it uses the internal network. Both paths exist and they are different.
The service listens on 127.0.0.1 inside its own container. Postgres by default binds localhost only in some images; the database must bind 0.0.0.0 to be reachable from other containers. That's why official images set listen_addresses='*' — check yours if it's custom.
Depends_on isn't readiness. depends_on orders container starts, not service readiness; your app boots before Postgres accepts connections and dies with the same error. Healthchecks with depends_on: condition: service_healthy fix the race.
Diagnosis in one command
docker compose exec app getent hosts postgres — if DNS resolves, the network is fine and the bug is the host/port in your config. If it doesn't, your services aren't on the same network (check networks: blocks). Practicing this on a throwaway server — like an hourly VM — makes the namespace model click for good.