Skip to main content

Docker · troubleshooting

Connection refused between containers

Almost always one of three things: using `localhost` when you mean the other container's name, the containers not sharing a network, or the target application bound to its own loopback so nothing outside the container can reach it.

Run this first

step 1 of 3
docker exec <app> sh -c 'getent hosts db || nslookup db'

Look for An address means DNS works, so the problem is further along. No answer means a network problem.

Separates resolution from connection immediately, which is the fork in this whole investigation.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

Does the hostname resolve from inside the calling container?

Run `docker exec <app> getent hosts <name>`. This splits the investigation in two.

Cause space

5 of 5 still possible

  • Connecting to `localhost` from inside a containerCommon
  • The containers are not on the same networkCommon
  • The target application is bound to its own loopbackCommon
  • The target was not accepting connections yetOccasional
  • Connecting to the published host port instead of the container portOccasional

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Check it with a tool

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    docker exec <app> sh -c 'getent hosts db || nslookup db'

    Separates resolution from connection immediately, which is the fork in this whole investigation.

    Look for An address means DNS works, so the problem is further along. No answer means a network problem.

  2. Step 2

    docker exec <db> sh -c 'ss -tlnp 2>/dev/null || netstat -tlnp'

    If the name resolves, the next question is whether anything is actually listening on a reachable address.

    Look for `0.0.0.0:<port>` rather than `127.0.0.1:<port>`.

  3. Step 3

    docker inspect <app> --format '{{json .NetworkSettings.Networks}}'

    Confirms which networks the containers share, which determines whether names can resolve at all.

    Look for A user-defined network appearing in both containers' output.

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

Connecting to `localhost` from inside a container

Common

The connection string was written for a development machine where everything ran on one host. Inside a container, `localhost` is that container's own loopback interface, so the connection is refused by the container itself — nothing ever leaves it.

Confirm

docker exec <app> printenv | grep -iE 'host|url|_addr'

Any `localhost` or `127.0.0.1` in a connection string pointing at a different service. That is the fault.

Fix

  • Use the other container's service name as the hostname: `postgres://user:pass@db:5432/app` rather than `@localhost:5432`.
  • Keep the container port, not the published host port. Container-to-container traffic goes direct.
# compose
services:
  app:
    environment:
      DATABASE_URL: postgres://user:pass@db:5432/app
  db:
    image: postgres:16

`db` here is the service name, which Docker's embedded DNS resolves on the shared network.

The containers are not on the same network

Common

Name resolution between containers only works on a user-defined network. Containers started separately with `docker run` land on the default bridge, where they have addresses but no name resolution — so the hostname fails to resolve rather than the connection being refused.

Confirm

docker inspect <app> --format '{{json .NetworkSettings.Networks}}' && docker inspect <db> --format '{{json .NetworkSettings.Networks}}'

A network name present in both outputs. If they share none, that is the cause.

Fix

  • Put both on the same user-defined network. Compose does this automatically for services in one file, which is why the problem usually appears with plain `docker run`.
  • Distinguish the two error shapes: a name that does not resolve is a network problem, and a connection actively refused means resolution worked and nothing was listening.
docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name app --network appnet myapp

The target application is bound to its own loopback

Common

The name resolves, the network is shared, and the connection is still refused — because the target process is listening on 127.0.0.1 inside its container. Traffic arriving on the container's network interface has nothing accepting it.

Confirm

docker exec <db> sh -c 'ss -tlnp 2>/dev/null || netstat -tlnp'

The Local Address column. `127.0.0.1:5432` is the problem; `0.0.0.0:5432` or `*:5432` is correct.

Fix

  • Configure the application to bind `0.0.0.0`. Many frameworks default to localhost when no host is configured, which is safe on a laptop and wrong in a container.
  • This is the same three-layer confusion as EXPOSE versus published ports, one level along — the bind address is a property of the application, not of Docker.

The target was not accepting connections yet

Occasional

Container start order is not readiness. A dependent service can start, resolve the name, and be refused because the database is still initialising. `depends_on` without a condition waits for the container to be created, not for the application to be ready.

Confirm

docker compose logs db | head -30

Whether the database announced it was ready before or after your application's first failed connection. Compare timestamps.

Fix

  • Add a healthcheck to the dependency and use `depends_on: condition: service_healthy`.
  • Better, make the client retry with backoff. Dependencies restart in production too, and start-order configuration does not help then.
services:
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 10
  app:
    depends_on:
      db:
        condition: service_healthy

Connecting to the published host port instead of the container port

Occasional

With `-p 5433:5432`, other containers still reach the database on 5432 — the published mapping is for host traffic. Using 5433 between containers connects to a port nothing is listening on.

Confirm

docker port <db>

Output like `5432/tcp -> 0.0.0.0:5433`. The left side is what containers use; the right side is for the host.

Fix

  • Use the container port in inter-container connection strings, and the host port only from the host.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

What is actually happening

Inside a container, `localhost` means *that container*. It does not mean the host, and it certainly does not mean the other container in your compose file. This is the single most common cause and it catches almost everybody once.

Containers on a user-defined Docker network reach each other by service or container name, which Docker resolves through its embedded DNS. On the default bridge network they cannot resolve each other by name at all.

And even with the right name on the right network, the target application has to be listening on an address that is reachable from outside its own container. A process bound to 127.0.0.1 is unreachable by design.

Published ports are irrelevant to container-to-container traffic. `-p` maps a host port inward; two containers on a shared network talk directly on the container port without any publishing at all.

How to tell this is your problem
WhereWhat you see
Application logs`ECONNREFUSED 127.0.0.1:5432`, or `getaddrinfo ENOTFOUND db`.
docker network inspect <network>Only one of the two containers listed in Containers.
docker exec <a> ss -tlnpThe target listening on `127.0.0.1:5432` rather than `0.0.0.0:5432`.

How to know it is actually fixed

  • `docker exec <app> sh -c 'getent hosts db'` returns an address.
  • The application connects on start-up without retrying, and its logs show no connection errors.
  • The connection survives restarting the dependency, which confirms the client retries rather than depending on start order.

Stopping it happening again

  • Use service names and container ports in configuration, and treat `localhost` in a container's config as a bug on sight.
  • Give dependencies healthchecks and depend on health rather than on creation.
  • Make clients retry with backoff. Start-order configuration only helps at start-up, and dependencies fail at other times too.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Related

Connection refused between containers — causes, diagnosis and fix | DevOps Insights