How to Access the Host from a Docker Container
A container has its own network namespace. Inside it, localhost and 127.0.0.1 point to the container itself, not to the machine running Docker. An app in a container that connects to localhost:5432 looks for PostgreSQL inside its own container and gets "connection refused".
To reach a service on the host you need two things: a name or address for the host that the container can use, and a host service that listens on an interface the container can reach. Most failures are the second one.
Docker Desktop (macOS and Windows)
Docker Desktop provides the special name host.docker.internal, which resolves to the host from every container:
docker run --rm curlimages/curl -s http://host.docker.internal:3000
Use that name in connection strings wherever you would have written localhost:
docker run -d --name my-app \
-e DATABASE_URL=postgres://app:secret@host.docker.internal:5432/app \
my-app:1.0
Docker Desktop forwards this traffic through its own proxy on the host side, so even a service bound to 127.0.0.1 is usually reachable. That is convenient, and it is also why the same setup often breaks when it moves to a Linux server.
Docker Engine on Linux
On Linux, host.docker.internal does not exist unless you add it. The --add-host flag accepts the special value host-gateway (Docker Engine 20.10 and later), which resolves to the host's address on the default bridge, usually 172.17.0.1 on the docker0 interface:
docker run --rm --add-host=host.docker.internal:host-gateway \
curlimages/curl -s http://host.docker.internal:3000
The same in Compose:
services:
app:
image: my-app:1.0
extra_hosts:
- "host.docker.internal:host-gateway"
Adding the entry everywhere means one configuration works on a Mac laptop and on a Linux server. Podman provides a similar name, host.containers.internal, out of the box.
Make the host service listen where containers can reach it
On Linux, a service bound to 127.0.0.1 accepts connections only from the host's loopback interface. Traffic from a container arrives on docker0 or another bridge interface and is refused. Check what the service listens on:
sudo ss -tlnp | grep 5432
If you see 127.0.0.1:5432, the service is loopback only. You have two options:
- Bind to the bridge address as well, for example
172.17.0.1. Containers and the host can connect, the outside network cannot. The interface must exist when the service starts, so the service has to start after Docker. - Bind to
0.0.0.0and restrict access with a firewall, because the service now listens on every interface, including public ones.
For PostgreSQL that means two files. In postgresql.conf:
listen_addresses = 'localhost,172.17.0.1'
And in pg_hba.conf, allow the Docker address range:
host all all 172.16.0.0/12 scram-sha-256
Development servers usually need an explicit flag:
flask run --host 0.0.0.0
python3 -m http.server 8000 --bind 0.0.0.0
Firewall rules
Traffic from a container to the host passes the host firewall like any other client. With ufw, or any setup that drops incoming traffic by default, allow the Docker subnet on the port you need:
sudo ufw allow from 172.17.0.0/16 to any port 5432 proto tcp
Containers started by Compose sit on their own network with a different subnet. Look it up instead of guessing:
docker network inspect my-app_default -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
Host network mode
With --network host the container shares the host's network stack. localhost inside the container is the host, and a service bound to 127.0.0.1 on the host is reachable directly:
docker run --rm --network host my-app:1.0
This works natively on Linux. Docker Desktop supports it from version 4.34 after you turn on "Enable host networking" in Settings, Resources, Network. Published ports (-p) are ignored in this mode, and the container gets no network isolation: it can bind any host port and see every host interface. Use it for local tools and network debugging, not as a default.
Using the gateway IP directly
You can look up the bridge gateway yourself:
docker network inspect bridge -f '{{(index .IPAM.Config 0).Gateway}}'
# or from inside a container
ip route | awk '/default/ {print $3}'
Hardcoding 172.17.0.1 works until it does not. Each user-defined network has its own gateway, and address pools can be changed in /etc/docker/daemon.json. Prefer host-gateway, which follows the daemon configuration.
Unix sockets instead of TCP
When the host service exposes a Unix socket, mount the socket directory and skip networking entirely:
docker run -d --name my-app \
-v /var/run/postgresql:/var/run/postgresql \
-e DATABASE_URL="postgresql:///app?host=/var/run/postgresql" \
my-app:1.0
No listening address, no firewall rule. The container user needs permission on the socket file, and the server's rules for local connections apply. This works on Linux hosts.
Adding other host entries
--add-host writes extra lines into the container's /etc/hosts, so it also works for any name you want to pin to an address:
docker run --rm \
--add-host=db.internal:10.0.0.5 \
--add-host=api.internal=[2001:db8::10] \
alpine:3 cat /etc/hosts
Repeat the flag for each entry. IPv6 addresses go in square brackets. In Compose, add more lines under extra_hosts.
Do not edit /etc/hosts inside a running container. Docker manages that file, and your changes disappear when the container is recreated. For names that many services need, real DNS is the better place. For container-to-container traffic, put the containers on the same user-defined network and use service or container names.
Troubleshooting checklist
- The name resolves inside the container:
docker exec my-app getent hosts host.docker.internal - The host service listens on a non-loopback address (
ss -tlnp). - The host firewall allows the Docker subnet on that port.
- The app config uses
host.docker.internal, notlocalhost. - A throwaway container can connect:
docker run --rm --add-host=host.docker.internal:host-gateway \ curlimages/curl -v http://host.docker.internal:3000
Quick reference
# Docker Desktop: works out of the box
http://host.docker.internal:PORT
# Linux: add the name yourself
docker run --add-host=host.docker.internal:host-gateway ...
# Compose: extra_hosts: ["host.docker.internal:host-gateway"]
# Share the host network stack (Linux, Docker Desktop 4.34+ opt-in)
docker run --network host ...
# Custom /etc/hosts entry
docker run --add-host=db.internal:10.0.0.5 ...
# Check what a host service listens on
sudo ss -tlnp
