Toolshed

A growing library of browser tools and deep technical guides for IT professionals.

← All guides

Diagnosing "connection refused" between Docker Compose services on a shared network

3 min read

Two containers in the same docker-compose.yml, one can't reach the other, and the error is some variant of connection refused or could not translate host name. This is almost always one of four causes, in rough order of likelihood.

Cause 1: using localhost instead of the service name

Inside a container, localhost means that container, not the host machine or a sibling container. If api tries to reach db via localhost:5432, it's asking its own container for a database that isn't running there.

services:
  db:
    image: postgres:16
  api:
    build: ./api
    environment:
      # wrong: DATABASE_URL: postgres://user:pass@localhost:5432/app
      DATABASE_URL: postgres://user:pass@db:5432/app   # right — use the service name

Compose's built-in DNS resolves service names to the right container automatically, as long as both services are on the same network.

Cause 2: services aren't actually on the same network

By default, Compose puts every service in one project on a single shared network, so this usually isn't the issue — until someone adds an explicit networks: block to one service and forgets the other needs it too. If either service has its own networks: list, both need to share at least one network:

services:
  api:
    networks: [backend]
  db:
    networks: [backend]   # must match, or api can't resolve/reach db
networks:
  backend:

Check with docker network inspect <project>_default (or your named network) — both containers should be listed.

Cause 3: the target service is up but not ready yet

The container is running, but the process inside it (database, API server) hasn't finished starting. depends_on alone only waits for the container to start, not the application inside it to be ready — this is a distinct, common issue with its own fix (a healthcheck plus depends_on: condition: service_healthy), which is common enough to warrant its own writeup rather than a paragraph here.

Cause 4: wrong port, or expose vs published port confusion

expose makes a port reachable to other containers on the same network but not to your host machine; ports: publishes it to the host too. Connection issues between containers are essentially never fixed by adding a ports: mapping — that only affects host access. Double check the port the target service actually listens on inside the container (not the host-mapped port) matches what the caller is using:

services:
  db:
    image: postgres:16
    expose: ["5432"]        # reachable by other containers on this network
    ports: ["5433:5432"]    # reachable from the host at localhost:5433 — irrelevant to `api`

Quick checklist

  1. Is the caller using the service name, not localhost or 127.0.0.1?
  2. docker network inspect — are both containers on the same network?
  3. Is the target's application actually finished starting (check its logs, not just docker compose ps)?
  4. Does the port match what's listening inside the container, not a host-mapped port?

Sources: Docker Compose networking documentation (service-name DNS resolution, default network behavior); Docker Engine documentation on expose vs ports; verified against a real multi-service Compose setup while building this project's automation.