PostgreSQL service discovery failure in Docker Compose

Problem Description

In a local AI training sandbox the microservices that compose the pipeline cannot locate the PostgreSQL instance. The typical error observed in the consuming service logs is:


2023-07-21T14:12:03.123Z microservice-a[12]: could not translate host name "postgres" to address: Name or service not known

Other variants that appear during the investigation include:

  • FATAL: could not connect to server: Connection refused (0x0000274D/10061)
  • psql: error: could not connect to server: No such file or directory
  • ERROR: could not resolve hostname "db" – DNS lookup failed inside container

The failure prevents the AI training pipeline from persisting model artifacts, loading configuration data, and generally halts the end‑to‑end workflow.

Root Cause Analysis

The core of the problem is a mismatch between the Docker Compose network topology and the DNS expectations of PostgreSQL client libraries. According to the PostgreSQL “Database Connection Settings” chapter, the client resolves the host parameter using the system resolver, which in a container is the Docker embedded DNS server. If the service name is not present in that DNS view, the resolver returns ENOTFOUND, leading to the “could not translate host name” error.

Evidence from the community shows two typical patterns that produce this state:

  • A custom network is defined for GPU‑enabled containers, while the postgres service remains on the default network (GitHub issue 6715).
  • Explicit network_mode: bridge is set on the PostgreSQL service, which isolates it from the user‑defined overlay network used by the AI inference service (GitHub issue 8329).

In both cases the Docker embedded DNS does not publish the postgres name into the network namespace of the consuming containers, so DNS resolution fails before any authentication (as described in Chapter 33 “Client Authentication”).

Investigation and Debugging

The following step‑by‑step checks isolate the DNS mismatch:

  1. Inspect the Compose networks
    docker-compose config | grep networks -A 5

    Expected output shows a single default network unless a custom one is declared.

  2. Verify which network each container is attached to
    docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.NetworkID}} {{end}}' $(docker ps -q)

    Look for the postgres container ID and compare it with the IDs of the AI services.

  3. Test DNS resolution from a consumer container
    docker exec -it microservice-a sh -c 'getent hosts postgres || echo "lookup failed"'

    If the command prints “lookup failed”, the DNS entry is missing.

  4. Capture a packet trace (optional)
    docker exec -it microservice-a tcpdump -i any -n udp port 53 -c 5

    The trace should show a DNS query to 127.0.0.11:53. A lack of response indicates the embedded DNS does not know the name.

  5. Check PostgreSQL container logs for bind address
    docker logs postgres | grep "listening on"

    The official image logs “listening on all IP addresses” by default; if it is bound only to 127.0.0.1, external containers cannot reach it.

Resolution

The fix consists of aligning all services on a single user‑defined network and removing any conflicting network_mode directives. Below is a before/after comparison of the docker-compose.yml file.

Before

version: "3.8"
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    network_mode: bridge   # <-- isolates container
  microservice-a:
    build: ./microservice-a
    depends_on:
      - postgres
    # uses default network
  inference:
    build: ./inference
    networks:
      - gpu_net          # custom network for GPU containers

networks:
  gpu_net:
    driver: bridge

After

version: "3.8"
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    networks:
      - app_net          # <-- attach to shared network
  microservice-a:
    build: ./microservice-a
    depends_on:
      - postgres
    networks:
      - app_net
  inference:
    build: ./inference
    networks:
      - app_net
      - gpu_net          # keep GPU network if needed

networks:
  app_net:
    driver: bridge
  gpu_net:
    driver: bridge

Key changes:

  • Removed network_mode: bridge from the PostgreSQL service.
  • Created an explicit app_net bridge network and attached every service that needs database access.
  • Preserved the gpu_net for GPU‑specific traffic while also joining app_net so DNS resolution works.

After applying the updated compose file, bring the stack down and up to ensure a clean network recreation:

docker-compose down -v
docker-compose up -d

Validation

Confirm that DNS resolution now succeeds and the database connection is established:

  1. From a consumer container:
    docker exec -it microservice-a sh -c 'getent hosts postgres'

    Expected output: 172.20.0.2 postgres (IP will vary).

  2. Attempt a direct psql connection:
    docker exec -it microservice-a sh -c 'psql -h postgres -U postgres -c "SELECT 1;"'

    Expected output:

    
     ?column? 
    ----------
            1
    (1 row)
    
  3. Check the PostgreSQL logs for successful authentication:
    docker logs postgres | grep "authentication successful"

    A line similar to 2023-07-21 14:15:02.123 UTC [12] LOG: connection authorized: user=postgres database=postgres confirms the handshake.

Prevention and Best Practices

  • Define a single logical network for services that need to talk to each other. Use explicit network names instead of the default network to avoid accidental isolation.
  • Avoid network_mode: bridge on individual services. This overrides Compose’s network handling and prevents DNS propagation.
  • Declare depends_on only for start‑order, not for network visibility. All services on the same network can resolve each other regardless of dependencies.
  • Enable healthchecks that verify DNS resolution. Example:
    healthcheck:
      test: ["CMD", "pg_isready", "-h", "postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
  • Monitor Docker DNS errors. Alert on log patterns such as “could not translate host name” to catch mis‑configurations early.

Related Topic Hub: Data Infrastructure Troubleshooting Hub

FAQ

  1. Why does the error appear only after adding a GPU‑enabled external network?
    Because the PostgreSQL container remained on the default network while the GPU containers were attached to a separate network. Docker DNS only resolves names within the same network scope, so the new containers could not see postgres.
  2. Can I keep network_mode: bridge for the PostgreSQL container?
    No. Using network_mode: bridge creates an independent Docker bridge that is not part of the Compose‑managed network, breaking DNS resolution for sibling services.
  3. Is depends_on sufficient to guarantee name resolution?
    No. depends_on only controls container start order. DNS visibility is governed solely by network membership.
  4. What if I need the PostgreSQL container to be reachable from the host machine?
    Expose the port on the shared network (e.g., ports: ["5432:5432"]) or use docker run -p on the host. The DNS name is still resolved only inside containers on the same network.
  5. How can I debug DNS resolution without entering the container?
    Use docker network inspect <network_name> to view the list of containers and their assigned IPs. Verify that the service name appears under ContainersName entries.