Skip to content
ohsudo, Home
Docker & Containers

Docker: "connection refused" to localhost from a container — how to reach the host

Fix "connection refused" when a container connects to localhost: use host.docker.internal or a Compose service name, make the host service listen, and check the firewall.

Beginner4 min readApplies to Docker Engine 20.10+ · Docker Desktop · Docker Compose v2 · Linux · macOS · Windows

by OhSudo ··

Not sure this is your cause?Find it step by step: Website down →Find it step by step: Container won't start →Find it step by step: Can't connect to the database →

Part of a learning path:Docker: from first container to production

Your app runs in a container and should talk to a database (or an API) on the same machine. It works on your laptop without Docker. In the container:

Error: connect ECONNREFUSED 127.0.0.1:5432
psycopg2.OperationalError: connection to server at "localhost" (127.0.0.1), port 5432 failed: Connection refused

Inside a container, localhost is the container itself. Each container has its own network — its own 127.0.0.1 — and your database isn’t in there. Nothing listens on port 5432 inside the container, so the connection is refused.

Which fix is right depends on where the other service lives.

  1. Your app, in a containerconnects to localhost:5432
  2. The same containerits own 127.0.0.1: no database here(breaks here)
Each container has its own network, and its own 127.0.0.1. Nothing listens on the database's port there, so the connection is refused.

TL;DR

The other service runs… Connect to
In another container (same Compose file) The service name: db:5432
On the host itself host.docker.internal:5432 (on Linux, add host-gateway, see below)
On another machine Its real address or DNS name, as usual

1. The other service is a container too: use its name

With Docker Compose, all services of a project share a network and find each other by service name:

services:
app:
image: myapp:latest
environment:
DATABASE_URL: postgres://app:secret@db:5432/shop # "db", not "localhost"
depends_on:
- db
db:
image: postgres:17
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: shop

Use the port inside the network (5432), not a port you published on the host. The database doesn’t even need ports: for this — and it’s safer without. With plain docker run, put both containers on a user-defined network (docker network create app-net, then --network app-net); the default bridge network doesn’t resolve names.

The Docker Compose Generator sets this up for you, connection strings included.

2. The service runs on the host: host.docker.internal

On Docker Desktop (Windows, macOS), the name host.docker.internal points to your computer and works out of the box.

On Linux with Docker Engine, add it yourself — host-gateway is a special value that means “the host”:

services:
app:
image: myapp:latest
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
DATABASE_URL: postgres://app:secret@host.docker.internal:5432/shop

With docker run: --add-host=host.docker.internal:host-gateway.

3. Still refused? The host service only listens on 127.0.0.1

Now the request reaches the host — through the Docker bridge, from an address like 172.17.0.2 — and is refused again. Most databases listen only on 127.0.0.1 by default, and that address isn’t reachable from containers. Check:

Terminal window
sudo ss -tlnp | grep 5432
LISTEN 0 244 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=882,fd=6))

Make the service also listen on the Docker bridge address (usually 172.17.0.1, see ip -br addr show docker0), and allow connections from the containers’ network:

  • PostgreSQL: listen_addresses = 'localhost,172.17.0.1' in postgresql.conf, plus a host … 172.16.0.0/12 scram-sha-256 line in pg_hba.conf, then restart.
  • MySQL / MariaDB: bind-address = 127.0.0.1,172.17.0.1 (MySQL 8.0.13+), and an account for that network ('app'@'172.%').

Avoid 0.0.0.0 unless a firewall keeps the port closed to the internet.

Compose networks use other subnets than 172.17.0.0/16 — docker network inspect <name> shows them. If the host’s firewall (UFW, firewalld) filters incoming traffic, allow those subnets to the port too.

4. The other way round: host → container

The reverse problem — the container runs, the port is published (-p 8080:3000), and curl localhost:8080 on the host fails with Connection reset by peer or Empty reply from server. Usually the app inside the container listens on its own 127.0.0.1:3000, which the published port can’t reach. Make the app listen on all interfaces inside the container:

Terminal window
flask run --host 0.0.0.0
node server.js # with app.listen(3000, '0.0.0.0')
python -m http.server 3000 --bind 0.0.0.0

That’s safe: 0.0.0.0 inside the container is still only the container’s network; what the host exposes is decided by -p.

Note

network_mode: host (Linux only) puts the container in the host’s network, so localhost really is the host. It works, but it drops the isolation and ignores ports: — fine for a quick test or special cases, not as the default fix.

Shell, we have a problem!

Similar, but different: getaddrinfo ENOTFOUND db / could not translate host name "db" — the containers aren’t on the same network, or the service name is different; docker compose ps and docker network inspect show both. Bind for 0.0.0.0:5432 failed: port is already allocated is about publishing a port the host already uses — see Address already in use. And if the connection hangs instead of being refused, a firewall is dropping it: see “Connection timed out” vs “Connection refused”. To put a web server in front of containers, see Nginx with Docker Compose.

Found this useful? Share it.

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles