Your app container fails to reach db with getaddrinfo ENOTFOUND db or could not translate host name "db", even though both are defined in the same Compose file — a recurring local failure point during onboarding.

Diagnostic

From inside the failing container, try to resolve the peer service name and inspect which networks each container actually joined.

#!/usr/bin/env bash
set -euo pipefail
docker compose exec app getent hosts db || echo "resolution failed"
docker compose exec app cat /etc/resolv.conf
docker inspect -f '{{.Name}} -> {{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' \
  "$(docker compose ps -q app)" "$(docker compose ps -q db)"

Expected BAD output — the name does not resolve and the two containers sit on different networks:

resolution failed
nameserver 127.0.0.11
options ndots:0
/app-app-1 -> app_frontend
/app-db-1 -> app_backend

The nameserver 127.0.0.11 line is correct — that is Docker's embedded DNS. The real fault is that app and db are on different user-defined networks, so the embedded resolver has no record for db in app's scope.

Root cause

Docker's embedded DNS server runs at 127.0.0.11 inside every container on a user-defined network and resolves service names to container IPs — but only for containers attached to the same network. Service-name DNS silently fails when the two services are placed on different networks (often by per-service networks: lists that do not overlap), when a service is reached before it has registered (a timing gap that depends_on alone does not close, because it waits for start, not readiness), or when code targets the wrong name — Compose registers both the service key and the container name, and custom aliases add more. The embedded resolver is working; it just has nothing to answer with. This is the local-container view of the routing covered in configuring local DNS for microservice routing.

Resolution

  1. Put both services on a shared user-defined network so the embedded DNS scope overlaps.
  2. Add explicit aliases if code references a hostname other than the service key.
  3. Gate the dependent on readiness with depends_on: condition: service_healthy, not bare depends_on.
  4. Recreate the stack so network membership and DNS records are rebuilt.
# docker-compose.yml
services:
  app:
    build: .
    depends_on:
      db:
        condition: service_healthy
    networks:
      - appnet
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: localdev
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 2s
      timeout: 3s
      retries: 10
    networks:
      appnet:
        aliases:
          - database   # app can use db OR database
networks:
  appnet:
    driver: bridge

Then rebuild membership and confirm the resolver answers:

#!/usr/bin/env bash
set -euo pipefail
docker compose down
docker compose up -d --wait
docker compose exec app getent hosts db

Expected output

The peer name now resolves to the embedded DNS record (an IP on the shared bridge), and both containers list the same network:

$ docker compose exec app getent hosts db
172.20.0.3      db

$ docker inspect -f '{{.Name}} -> {{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' app-app-1 app-db-1
/app-app-1 -> appnet
/app-db-1 -> appnet

Prevention

  1. Default to a single shared network and only segment when isolation is a real requirement — document the topology alongside mapping microservice dependencies for local dev.
  2. Always pair cross-service calls with depends_on: condition: service_healthy so the name exists and the service is ready — see resolving service startup-order and healthcheck races.
  3. Add a getent hosts <peer> probe to your onboarding health-check script.

macOS / Windows (Docker Desktop): the embedded resolver lives inside the Linux VM; you cannot resolve service names from the host. Use localhost:<published-port> from the host and service names only between containers. WSL2: a stale /etc/resolv.conf (from generateResolvConf=false) can shadow 127.0.0.11; let Docker manage container resolv.conf and avoid injecting host DNS into containers. Apple Silicon (ARM64): no DNS behavior difference, but a service that exits on startup never registers a DNS record — confirm the peer is actually running first.

Rollback

#!/usr/bin/env bash
set -euo pipefail
git checkout -- docker-compose.yml && docker compose up -d --force-recreate   # restore prior networks