Ordered by how often each one turns out to be the answer.
Connecting to `localhost` from inside a container
CommonThe connection string was written for a development machine where everything ran on one host. Inside a container, `localhost` is that container's own loopback interface, so the connection is refused by the container itself — nothing ever leaves it.
Confirm
docker exec <app> printenv | grep -iE 'host|url|_addr'
Any `localhost` or `127.0.0.1` in a connection string pointing at a different service. That is the fault.
Fix
- Use the other container's service name as the hostname: `postgres://user:pass@db:5432/app` rather than `@localhost:5432`.
- Keep the container port, not the published host port. Container-to-container traffic goes direct.
# compose
services:
app:
environment:
DATABASE_URL: postgres://user:pass@db:5432/app
db:
image: postgres:16`db` here is the service name, which Docker's embedded DNS resolves on the shared network.
The containers are not on the same network
CommonName resolution between containers only works on a user-defined network. Containers started separately with `docker run` land on the default bridge, where they have addresses but no name resolution — so the hostname fails to resolve rather than the connection being refused.
Confirm
docker inspect <app> --format '{{json .NetworkSettings.Networks}}' && docker inspect <db> --format '{{json .NetworkSettings.Networks}}'A network name present in both outputs. If they share none, that is the cause.
Fix
- Put both on the same user-defined network. Compose does this automatically for services in one file, which is why the problem usually appears with plain `docker run`.
- Distinguish the two error shapes: a name that does not resolve is a network problem, and a connection actively refused means resolution worked and nothing was listening.
docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name app --network appnet myapp
The target application is bound to its own loopback
CommonThe name resolves, the network is shared, and the connection is still refused — because the target process is listening on 127.0.0.1 inside its container. Traffic arriving on the container's network interface has nothing accepting it.
Confirm
docker exec <db> sh -c 'ss -tlnp 2>/dev/null || netstat -tlnp'
The Local Address column. `127.0.0.1:5432` is the problem; `0.0.0.0:5432` or `*:5432` is correct.
Fix
- Configure the application to bind `0.0.0.0`. Many frameworks default to localhost when no host is configured, which is safe on a laptop and wrong in a container.
- This is the same three-layer confusion as EXPOSE versus published ports, one level along — the bind address is a property of the application, not of Docker.
The target was not accepting connections yet
OccasionalContainer start order is not readiness. A dependent service can start, resolve the name, and be refused because the database is still initialising. `depends_on` without a condition waits for the container to be created, not for the application to be ready.
Confirm
docker compose logs db | head -30
Whether the database announced it was ready before or after your application's first failed connection. Compare timestamps.
Fix
- Add a healthcheck to the dependency and use `depends_on: condition: service_healthy`.
- Better, make the client retry with backoff. Dependencies restart in production too, and start-order configuration does not help then.
services:
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10
app:
depends_on:
db:
condition: service_healthyConnecting to the published host port instead of the container port
OccasionalWith `-p 5433:5432`, other containers still reach the database on 5432 — the published mapping is for host traffic. Using 5433 between containers connects to a port nothing is listening on.
Confirm
docker port <db>
Output like `5432/tcp -> 0.0.0.0:5433`. The left side is what containers use; the right side is for the host.
Fix
- Use the container port in inter-container connection strings, and the host port only from the host.