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.
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:5432psycopg2.OperationalError: connection to server at "localhost" (127.0.0.1), port 5432 failed: Connection refusedInside 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.
- Your app, in a containerconnects to localhost:5432localhost(fails)
- The same containerits own 127.0.0.1: no database here(breaks here)
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: shopUse 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/shopWith 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:
sudo ss -tlnp | grep 5432LISTEN 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'inpostgresql.conf, plus ahost … 172.16.0.0/12 scram-sha-256line inpg_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:
flask run --host 0.0.0.0node server.js # with app.listen(3000, '0.0.0.0')python -m http.server 3000 --bind 0.0.0.0That’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.