01
Read the destination before changing the server
Record the failing host and port from the application configuration, along with the service that emitted the error. In the common failure, the API tries localhost:15432 because that is the address used by a database client on the host. Inside the API container, localhost refers to that container, not to the database service.
Treat error classes separately. Connection refused means a TCP connection was rejected at that destination; it is not proof of a bad database password. Name-resolution failures, timeouts, authentication failures, and protocol errors require different next checks. Preserve the original error before editing configuration.
02
Compare the two address spaces
Compose services on a shared network can discover each other by service name. With a database service named db listening internally on 5432 and published as 15432:5432, an API on that network normally uses db:5432. A host-side client uses the published host address and port instead.
Write both paths down. For example: host client → localhost:15432 → db:5432; API container → db:5432. If those paths are mixed, changing the host mapping cannot repair the API’s destination. Avoid depending on a container IP, because recreating the service can change it.
# Host-side inspection; does not change the stack.
docker compose ps
docker compose port db 5432
docker compose logs --tail=80 api db
# Inspect configured networks without dumping container environment variables.
docker inspect --format '{{json .NetworkSettings.Networks}}' "$(docker compose ps -q api)"
docker inspect --format '{{json .NetworkSettings.Networks}}' "$(docker compose ps -q db)"03
Use the service name and internal port
The excerpt below publishes a database port only for host tools. The API uses a different host and port because it is a peer on the Compose network. Environment variable names must match what your application actually reads; DB_HOST is an example, not a Compose feature.
When services belong to different Compose projects, verify that they deliberately share a network. A matching service name alone does not join networks. Keep database exposure limited to the interfaces that need it; adding a public port is not a substitute for correct network membership.
services:
api:
build: ./api
environment:
DB_HOST: db
DB_PORT: "5432"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set a local test password}"
ports:
- "127.0.0.1:15432:5432"04
Check the listener and startup timing independently
If name and port are correct, inspect the database listener and network membership. A service bound only to its own loopback interface will not accept connections from a peer container. Prefer the database’s own diagnostics over assuming every image contains curl, ping, or a shell.
A service can also be started without being ready to accept work. Compose dependency ordering and a health-based dependency condition can help with initial startup, but the application still needs bounded retries and reconnection behavior for later outages. Do not confuse a timing fix with an address fix.
05
Verify from the same place that failed
Restart or recreate only the service whose connection configuration changed, using your normal deployment process. Make the API perform a real database operation and compare its new error or success with the original. A host-side database client passing again is not the decisive test.
Recheck after a controlled restart of the dependency in a disposable environment. The useful outcome is that the API reaches the intended database, authenticates, and recovers as designed. A container marked running, by itself, confirms none of those application-level properties.
Know the limits
This guide covers ordinary bridge-network Compose services. Host networking, external databases, proxies, and platform-specific host access require their own address model. The YAML is an illustrative fragment, not a production database deployment.
Sources & further reading
A technical publication by CoreTrailKit.
Browse all articles