Docker Compose Networking: How Service Name DNS Resolution Works

1 min readCloud Infrastructure

Docker Compose creates a default bridge network where each service's name is registered as a DNS hostname. Containers reach each other by service name, not by IP or localhost. Understanding how Docker's embedded DNS works — and when it doesn't — prevents the most common Docker Compose connectivity bugs.

dockercontainersdns

How Docker Compose DNS works

When Docker Compose starts a project, it creates a default network named <project-name>_default. All services defined in docker-compose.yml are automatically attached to this network. Docker's embedded DNS server registers each service's name as a resolvable hostname on the network.

services:
  app:
    image: my-app
    environment:
      DB_HOST: postgres    # resolves to the postgres container's IP
      REDIS_URL: redis://redis:6379

  postgres:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret

  redis:
    image: redis:7

When app connects to DB_HOST=postgres, Docker's DNS resolves postgres to the current IP of the postgres container. If postgres restarts and gets a new IP, the DNS record updates automatically — no hardcoded IPs needed.

localhost inside a container never reaches another container — each has its own network namespace

GotchaDocker Networking

Container network isolation means each container has a separate loopback interface. localhost (127.0.0.1) inside container A is container A's loopback only — it doesn't reach container B or the host machine. Cross-container connectivity requires using the service name as the hostname.

Prerequisites

  • Linux network namespaces
  • DNS resolution
  • Docker bridge networking

Key Points

  • localhost in a container refers to the container itself — not the host, not other containers.
  • Service names are DNS hostnames on the Compose-created network, resolving to container IPs.
  • Docker's DNS updates dynamically when containers restart and get new IPs.
  • The default bridge network (docker0) does NOT support DNS by service name — only user-defined networks do.

Service name resolution in practice

services:
  app:
    image: my-app
    environment:
      # These all work — service names resolve to container IPs
      DB_HOST: postgres
      CACHE_HOST: redis
      QUEUE_HOST: rabbitmq

      # This does NOT work — localhost is container A's own loopback
      # DB_HOST: localhost   # WRONG

Connecting from inside a container to verify DNS works:

# Shell into the app container
docker exec -it app-container bash

# Check DNS resolution
nslookup postgres
# Server:         127.0.0.11    (Docker's embedded DNS)
# Address:        127.0.0.11#53
# Name:   postgres
# Address: 172.18.0.3

# Test connectivity
curl -v http://postgres:5432
nc -zv redis 6379

127.0.0.11 is Docker's embedded DNS resolver — it's present in every container on a user-defined network.

Custom networks and multi-network setups

Compose creates one default network. You can define additional networks for service isolation:

services:
  web:
    image: nginx
    networks:
      - frontend

  api:
    image: my-api
    networks:
      - frontend
      - backend    # api can reach both web and db

  db:
    image: postgres:16
    networks:
      - backend    # db is NOT reachable from web

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true    # no external internet access from this network

web and db are on separate networks and cannot reach each other directly. api bridges them. internal: true on backend prevents containers on that network from reaching the internet — good for database networks that shouldn't have outbound access.

📝Extra hosts and custom DNS resolution

For cases where you need to override DNS resolution or add entries not available through Docker's DNS:

services:
  app:
    image: my-app
    extra_hosts:
      - "legacy-api:192.168.1.100"    # adds to /etc/hosts
      - "host.docker.internal:host-gateway"   # reach the Docker host machine

host.docker.internal is a special hostname that resolves to the Docker host's IP from inside a container. On Linux, it requires host-gateway as the IP value. On macOS and Windows (Docker Desktop), it resolves automatically without extra configuration.

For accessing services running on the host machine during development:

services:
  app:
    image: my-app
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      EXTERNAL_API: http://host.docker.internal:3000

This is useful when you have a service running locally (e.g., a hot-reload dev server) that the containerized app needs to reach.

A Docker Compose app service tries to connect to a database using DB_HOST=localhost and gets 'connection refused'. The postgres service is running and healthy. What is wrong?

easy

Both services are defined in the same docker-compose.yml. The postgres service starts before app (depends_on). The app container is running.

  • AThe postgres container needs to expose port 5432 in the docker-compose.yml
    Incorrect.The ports directive exposes a container's port to the host machine. Inter-container communication on the same Docker network doesn't require ports to be exposed — it works on the container's internal port directly.
  • Blocalhost inside the app container refers to the app container's own loopback interface, not the postgres container — use DB_HOST=postgres (the service name) instead
    Correct!Each container has its own network namespace and loopback. localhost (127.0.0.1) inside the app container is the app container's own loopback — no postgres listener is bound there. Docker Compose registers each service name as a DNS hostname on the shared network. DB_HOST=postgres resolves to the postgres container's IP address on the Compose network.
  • Cdepends_on doesn't guarantee postgres is ready to accept connections when app starts
    Incorrect.This is also true — depends_on waits for the container to start, not for postgres to be ready to accept connections. But it's a secondary issue; the primary problem is localhost not reaching the postgres container at all.
  • DThe app service needs to be on the same network as postgres using an explicit networks: configuration
    Incorrect.When no explicit networks are defined, Docker Compose adds all services to the default project network automatically. Both services are already on the same network.

Hint:What does localhost resolve to inside a container?