Article start

Docker Container Networking Explained: localhost, Service DNS, Ports, and Custom Networks

Learn why localhost usually points to the current container, how Compose service discovery works, when ports must be published, and how separate networks reduce unnecessary connectivity.

Many container connection bugs come from using the right port with the wrong point of view. A developer publishes PostgreSQL as 15432:5432, verifies that a desktop client reaches localhost:15432, and then gives the API container the same address. The API fails because its localhost is not the host and the host port is not the normal path between Compose services.

Docker Compose network showing host traffic through a published port and service traffic through internal DNS
Host-to-container and container-to-container traffic use different addresses.

Docker networking becomes predictable once you ask three questions: where does the client process run, which networks does it share with the server, and is the target port a host port or a container port?

localhost belongs to the current network namespace

In a normal Linux container, the loopback interface belongs to that container's network namespace. If an API container connects to 127.0.0.1:5432, it looks for PostgreSQL inside the API container. It does not automatically jump to the Docker host or another container.

The same rule explains why a process must listen on an appropriate interface. An application listening only on 127.0.0.1 inside its container cannot accept traffic arriving on the container's external interface. Web servers intended for container traffic commonly listen on 0.0.0.0 while access is restricted by network topology, firewalls, and authentication.

Compose gives services stable DNS names

By default, Compose creates one project network and connects each service to it. Services on that network can discover one another by service name. If the database service is named db, the API should use db as the host. Container IP addresses can change when services are recreated; the service name remains the stable reference.

Client locationDatabase mappingAddress to use
API container on the same Compose network15432:5432db:5432
Developer tool on the Docker host15432:5432localhost:15432
Container with no shared networkAny mappingNo service-name route until networking is configured

Open connections to an old container do not magically transfer after recreation. Clients must detect the closed connection, resolve the service name again, and reconnect. Stable DNS removes hard-coded IP addresses; it does not remove connection lifecycle management.

Host ports and container ports solve different problems

In "8080:80", port 8080 belongs to the host and port 80 belongs to the container. Publishing is required when a client outside the Compose network needs a route into the service. It is not required merely so two services on a shared network can communicate.

A database used only by the API should generally avoid a host port. That reduces accidental exposure and avoids host-port collisions. It is not a complete security control: authorization, credentials, network policy, patching, and encrypted transport still matter.

Segment a three-tier stack with named networks

services:
  proxy:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"
    networks:
      - frontend

  api:
    build: ./api
    environment:
      DB_HOST: db
      DB_PORT: "5432"
    networks:
      - frontend
      - backend

  db:
    image: postgres:17-alpine
    networks:
      - backend

networks:
  frontend:
  backend:
    internal: true

The proxy and API share frontend; the API and database share backend. The proxy has no route to the database because they share no network. Marking the backend network internal further restricts external connectivity through that network. The API remains attached to the frontend network, so its overall connectivity depends on both attachments.

This topology is useful, but avoid claiming that network separation alone creates a secure production system. A compromised API can still reach the database because that connection is required. Use least-privilege database roles and secrets, validate inputs, and monitor traffic.

DNS resolution is only the beginning of a successful connection. After resolving db, the client still needs a route on the shared network, a server listening on the expected interface and port, and a protocol-level handshake that succeeds. An authentication error proves more network progress than a timeout. Preserve that distinction in alerts: DNS failure, connection refusal, timeout, TLS error, and rejected credentials point to different layers and should not be collapsed into “database down.”

A systematic debugging workflow

docker compose config
docker compose ps
docker compose exec api getent hosts db
docker compose exec api sh -lc 'nc -vz db 5432'
docker network ls
docker network inspect myproject_backend

Minimal images may not contain getent, nc, or a shell. Do not mutate a production container just to debug it. Use an approved diagnostic image on the same network, or use runtime-native DNS and socket diagnostics already included in the application. Start by confirming the rendered Compose configuration, shared network membership, DNS resolution, listening address, and target port in that order.

Common networking mistakes

  • Using localhost to address a different container.
  • Using the published host port for traffic between services.
  • Hard-coding a container IP that changes after recreation.
  • Attaching services to different networks and expecting service DNS to bridge them.
  • Publishing a database port to every host interface without a real need.
  • Assuming expose is an authentication or firewall boundary.

Networking and startup intersect. The Compose health-check guide shows how to wait for db:5432 to become useful, while the Docker storage guide explains how the same database keeps its state when its container IP changes.

Frequently asked questions

Can a container use localhost to reach the Docker host?

Not as a portable default. Docker environments provide platform-specific host access mechanisms, but service-name DNS is the correct path between Compose services. Document any host dependency explicitly.

Do containers need ports entries to talk to one another?

No. Services sharing a network communicate on the destination container port. A ports entry publishes a route from the host or external interface.

Should an application use a container IP?

No. Use the service name or an intentional network alias. A recreated container can receive a different IP while keeping the same discoverable service name.

Where can I verify these rules?

Docker documents Compose networking and service discovery and provides a broader Docker networking overview.

Check the mental model

The linked mock includes service DNS, localhost, host-port mappings, storage, probes, and Kubernetes rollout behavior.

Quick quiz
DevOps

Docker and Kubernetes Production Readiness Mock Test

A practical assessment covering Docker Compose health checks, container DNS and storage, Kubernetes probes, resources, OOM diagnostics, and safer rolling updates.

15 questions25 min
Inline play

Start the trivia-style player right inside the article.

View details

Practical takeaway: identify the client's network namespace first, then connect through a shared network using a stable service name and the destination container port.

Discussion

0 comments

Sign in to share a question or add to the discussion.
Start the discussion

Ask a question or share what stood out to you.