Use the dependency's service name and container port from another container. Gate startup with a meaningful health check, then handle later disconnects in the application.
Start with the address the API actually uses
Suppose the API uses postgres://localhost:15432/app. That address suits a database port published on your laptop, but inside the API container localhost points back to the API container. Publishing a port does not change that meaning. Log the connection host and port without logging passwords.
The services below share Compose's default network. Their internal database endpoint is db:5432; a client on the Docker host uses 127.0.0.1:15432. If you define custom networks, confirm that API and database share at least one. Two separate Compose projects do not share their default networks automatically.
| Client | Database endpoint |
|---|---|
| API container | db:5432 |
| Docker host | 127.0.0.1:15432 |
| Different Compose project | Shared network required |
Give the database a readiness check
This example assumes a Node API Dockerfile in the current directory and an API listening on 0.0.0.0:8080. The demo password is for this disposable setup. The PostgreSQL major version is a teaching choice; production images and storage need their own policy.
Save this as compose.yaml. The health check targets db's local server. service_healthy waits for that check before starting api. Short-form depends_on orders startup without establishing readiness. Publishing 15432 is optional for service-to-service access.
services:
api:
build: .
environment:
DATABASE_URL: postgres://app:local-demo@db:5432/app
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:8080:8080"
db:
image: postgres:17
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: local-demo
POSTGRES_DB: app
ports:
- "127.0.0.1:15432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U app -d app"]
interval: 5s
timeout: 3s
retries: 12
start_period: 10sSeparate DNS, TCP and database errors
Run these commands from the directory containing compose.yaml. Replace service names if yours differ. The final two checks require a running Node API container; if api exits immediately, investigate its logs first or use a diagnostic container on the same network.
A lookup failure points toward service spelling or network membership. A successful lookup followed by a refused connection points toward the listener, port or startup state. Successful TCP only confirms that something accepts connections: authentication, database existence and SQL can still fail. Match the application error to the layer instead of changing several settings at once.
docker compose config --quiet
docker compose ps -a
docker compose logs --tail=50 db api
docker compose port db 5432
docker compose exec -T api node -e 'require("dns").lookup("db", (error, address) => { if (error) throw error; console.log(address); })'
docker compose exec -T api node -e 'const socket = require("net").createConnection({ host: "db", port: 5432 }); socket.setTimeout(3000); socket.on("connect", () => { console.log("TCP connected"); socket.end(); }); socket.on("timeout", () => { socket.destroy(); process.exitCode = 1; }); socket.on("error", error => { console.error(error.message); process.exitCode = 1; });'Make readiness match the next operation
pg_isready checks the PostgreSQL server's connection status. It does not prove that the API password works or that a required table and migration exist. If the API needs a schema upgrade first, run a separate migration job and make its successful completion an explicit dependency, or let the application report a precise initialization failure.
For the worked failure, fix localhost first. If the API then occasionally reports connection refused during startup, add the health check. If it reports relation does not exist after the server is healthy, investigate migrations. These are three different outcomes with three different fixes.
Handle replacement and downtime after startup
A healthy start is a point-in-time observation. A database can restart later, and recreating its container can change its IP. Keep db as the configured hostname and let the connection pool discard broken connections, resolve the name again and reconnect. Use bounded retries with backoff and a deadline.
A timeout after a write can leave its outcome unknown. Retry only when the operation is safe to repeat or protected by an idempotency design. Compose's depends_on restart: true concerns explicit Compose dependency operations; it is not a general runtime recovery mechanism for every database crash.
Verify one fix at a time
Check the resolved Compose configuration, start the stack, and compare database health with API startup logs. Then, in this disposable environment, restart db and observe whether the API reconnects without manual intervention. Success means the API can execute its actual database operation, not merely resolve a name.
Keep separate evidence for wrong-address, slow-start and later-disconnect cases. That matrix helps explain the diagnosis in an interview or incident. Continue with the broader Docker questions when you can explain each fix.
Quick answers
Frequently asked questions
Why does localhost fail between Compose containers?
Each container normally has its own network namespace. Inside api, localhost refers to api. Use db and the database's container port when both services share a Compose network.
Does depends_on wait for PostgreSQL?
The short form waits for startup order. The service_healthy condition waits for the dependency's configured health check to pass. The check must test the readiness your application needs.
Do I need to publish the database port?
Other containers on the same network use its container port without a published host port. Publish a port only if a host-side client needs access; this example binds it to loopback.
Will a health check fix an invalid password?
No. pg_isready can report an accepting server without verifying application authentication. Inspect the application's authentication error and test a real connection separately.
Source notes
References and review policy
Information checked on October 4, 2026. Section links identify sources for factual claims and technical explanations. Interpretations, practice scenarios and preparation recommendations are RecallDeck’s editorial work.
From reading to recall
Practice the full interview loop.
RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.