56.10 Reach the host from a container: host.docker.internal and extra_hosts
Why: the problem
Suppose we are building a service at Meesho on our laptop. The API runs in Docker, but the Postgres database, or a mock payment server, runs directly on the laptop on port 5432 or 9000. Inside the container we write localhost:9000 and get connection refused. That is correct behaviour: inside a container, localhost is the container itself, not our laptop. We need a name for the host. Docker Desktop gives one for free: host.docker.internal. On Linux it does not exist by default, but we can add it with --add-host host.docker.internal:host-gateway, or extra_hosts in Compose. extra_hosts also lets us add any custom name, for example to point api.partner.example at a test server. In this lesson we reach host services from containers on Desktop and Linux, fix the bind-address and firewall traps, and compare this with network_mode: host.
What: every term in plain English
- localhost in a container — The container's own loopback, not the host.
- host.docker.internal — DNS name for the host. Built into Docker Desktop.
- host-gateway — Special value Docker replaces with the host's IP on the bridge (usually 172.17.0.1).
- --add-host — Adds a line to the container's /etc/hosts.
- extra_hosts — Compose version of --add-host.
- docker0 — Default bridge interface on the Linux host.
- Bind address — 127.0.0.1 accepts only local connections; 0.0.0.0 or the bridge IP accepts containers.
- network_mode: host — Container shares the host network; Linux only, no isolation.
How: step by step
- Prove localhost fails. curl localhost:9000 inside.
- Use host.docker.internal. Works on Desktop.
- On Linux add host-gateway. --add-host or extra_hosts.
- Check the host bind address. Not only 127.0.0.1.
- Check the firewall. Allow docker0 or the bridge subnet.
- Add custom names. extra_hosts for test DNS.
Example 1: Why localhost fails, and host
Goal. Why localhost fails, and host.docker.internal on Docker Desktop.
Commands
python3 -m http.server 9000 --bind 0.0.0.0 &
docker run --rm curlimages/curl:8.10.1 -s -m 3 http://localhost:9000 || echo "refused: localhost is the container"
docker run --rm curlimages/curl:8.10.1 -s -o /dev/null -w '%{http_code}\n' http://host.docker.internal:9000
docker run --rm alpine:3.20 getent hosts host.docker.internal
What each line does
http.server 9000— A service on the host.localhost:9000 inside— Points at the container itself: nothing there.host.docker.internal— On Docker Desktop (Mac, Windows, Linux Desktop) resolves to the host.getent hosts— Shows the IP the name maps to.
Green output
refused: localhost is the container
200
192.168.65.254 host.docker.internal
Expected result. On Docker Desktop the special name just works. On plain Docker Engine for Linux, the last two commands fail without the next example.
Example 2: Docker Engine on Linux: host-gateway with --add-host and Compose extra_hosts
Goal. Docker Engine on Linux: host-gateway with --add-host and Compose extra_hosts.
compose.yaml
services:
api:
image: curlimages/curl:8.10.1
command: ["sh", "-c", "curl -s -o /dev/null -w 'host:%{http_code} ' http://host.docker.internal:9000; curl -s -o /dev/null -w 'partner:%{http_code}' http://api.partner.example:8080/health"]
extra_hosts:
- "host.docker.internal:host-gateway"
- "api.partner.example:10.0.0.42"
Commands
docker run --rm alpine:3.20 getent hosts host.docker.internal || echo "not defined on Linux"
docker run --rm --add-host host.docker.internal:host-gateway alpine:3.20 cat /etc/hosts | tail -2
docker run --rm --add-host host.docker.internal:host-gateway curlimages/curl:8.10.1 -s -o /dev/null -w '%{http_code}\n' http://host.docker.internal:9000
docker compose run --rm api
What each line does
not defined on Linux— Engine does not add the name by default.--add-host NAME:host-gateway— Docker writes the host's bridge IP into /etc/hosts./etc/hosts tail— Shows 172.17.0.1 host.docker.internal.extra_hosts— Same thing in Compose; works on Desktop too, so one file fits all.api.partner.example:10.0.0.42— Point a real-looking name to a test server, no DNS change.
Green output
not defined on Linux
172.17.0.1 host.docker.internal
200
host:200 partner:200
Expected result. Adding host-gateway in extra_hosts makes the same Compose file work on Linux, Mac and Windows.
Example 3: Fix the two traps: host bind address and firewall
Goal. Fix the two traps: host bind address and firewall. Compare with host networking.
Commands
python3 -m http.server 9100 --bind 127.0.0.1 &
docker run --rm --add-host host.docker.internal:host-gateway curlimages/curl:8.10.1 -s -m 3 http://host.docker.internal:9100 || echo "fails: host app listens on 127.0.0.1 only"
ss -ltn 'sport = :9100'
python3 -m http.server 9200 --bind 172.17.0.1 &
docker run --rm --add-host host.docker.internal:host-gateway curlimages/curl:8.10.1 -s -o /dev/null -w '%{http_code}\n' http://host.docker.internal:9200
sudo ufw allow in on docker0 to any port 5432 proto tcp
sudo ufw status | grep 5432
docker run --rm --network host curlimages/curl:8.10.1 -s -o /dev/null -w '%{http_code}\n' http://localhost:9100
What each line does
--bind 127.0.0.1— Host app refuses traffic from the docker0 bridge.ss -ltn— Confirms 127.0.0.1:9100.--bind 172.17.0.1— Listen on the bridge IP only: containers yes, internet no.ufw allow in on docker0— Host firewall must allow the bridge for that port; with Postgres also set listen_addresses and pg_hba.conf.--network host— Container uses host's network: localhost is the host. Linux only, no isolation, no -p.
Green output
fails: host app listens on 127.0.0.1 only
LISTEN 0 5 127.0.0.1:9100 0.0.0.0:*
200
Rule added
5432/tcp on docker0 ALLOW IN Anywhere
200
Expected result. Most 'host.docker.internal does not work' tickets are really a host app bound to 127.0.0.1 or a firewall rule. Host networking works but removes isolation; prefer extra_hosts.
Every command, step by step (plain docker and Compose)
-
Is the name there?
docker run --rm alpine:3.20 getent hosts host.docker.internalWhat it does: Resolves or not.
192.168.65.254 host.docker.internal -
Bridge IP
ip -4 addr show docker0What it does: Host IP seen by containers.
inet 172.17.0.1/16 -
Add name
docker run --add-host host.docker.internal:host-gateway ...What it does: Linux fix.
172.17.0.1 host.docker.internal -
Custom name
docker run --add-host api.partner.example:10.0.0.42 ...What it does: Test DNS override.
10.0.0.42 api.partner.example -
Host listening
ss -ltnp | grep 5432What it does: Bind address of host service.
0.0.0.0:5432 -
Host network
docker run --network host ...What it does: Share the host stack.
(no port mapping needed) -
Compose network gateway
docker network inspect -f '{{(index .IPAM.Config 0).Gateway}}' myapp_defaultWhat it does: Gateway for Compose networks.
172.20.0.1
When it is red 1
curl: (6) Could not resolve host: host.docker.internal
Cause. Docker Engine on Linux without the mapping.
Fix, step by step
- Add --add-host host.docker.internal:host-gateway or extra_hosts.
- Needs Docker 20.10 or newer.
When it is red 2
curl: (7) Failed to connect to host.docker.internal port 5432: Connection refused
Cause. Host service listens only on 127.0.0.1.
Fix, step by step
- Bind to 0.0.0.0 or 172.17.0.1.
- For Postgres set listen_addresses and a pg_hba.conf rule for 172.16.0.0/12.
When it is red 3
curl: (28) Connection timed out
Cause. Host firewall drops traffic from the bridge.
Fix, step by step
- Allow the port on docker0 or the Compose bridge in ufw or firewalld.
Practice tasks
Easy Task 1: Fail
Why: Prove.
What to do: curl localhost:9000 inside a container.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
docker run --rm curlimages/curl:8.10.1 http://localhost:9000Easy Task 2: Desktop
Why: Name.
What to do: Use host.docker.internal.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
curl http://host.docker.internal:9000Medium Task 3: Linux
Why: Fix.
What to do: Add host-gateway.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
docker run --add-host host.docker.internal:host-gateway ...Medium Task 4: Compose
Why: Portable.
What to do: Add extra_hosts.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
extra_hosts: ["host.docker.internal:host-gateway"]Medium Task 5: Hosts file
Why: See.
What to do: Read /etc/hosts inside.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
docker run --rm --add-host x:host-gateway alpine cat /etc/hostsMedium Task 6: Custom
Why: Override.
What to do: Map api.partner.example.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
--add-host api.partner.example:10.0.0.42Hard Task 7: Bind
Why: Trap.
What to do: Show 127.0.0.1 fails, 172.17.0.1 works.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
python3 -m http.server 9200 --bind 172.17.0.1Hard Task 8: Firewall
Why: Trap.
What to do: Allow 5432 on docker0.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
sudo ufw allow in on docker0 to any port 5432 proto tcpHard Task 9: Host network
Why: Compare.
What to do: Use --network host.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
docker run --network host ...Easy Task 10: Cleanup
Why: Done.
What to do: Stop the host servers.
- Open a terminal with Docker 20.10 or newer.
- Run a tiny server on your laptop or VM on port 9000: python3 -m http.server 9000.
- Run the steps and compare with the solution.
Solution
kill %1 %2 %3Homework: try at home
At home, run a small server on the host, reach it from a container on your OS, then bind it to 127.0.0.1 and explain why it breaks.
Recap
- localhost inside a container is the container.
- host.docker.internal is built into Docker Desktop.
- On Linux add host.docker.internal:host-gateway.
- extra_hosts also maps custom names.
- Check host bind address and firewall.
- Use host networking only when you really need it.
FAQ
Q. Should production use host.docker.internal?
Rarely. In production, services talk over real DNS or service discovery; this is mainly for local development.
Q. Does it work in Kubernetes?
No. Use a Service, ExternalName or the node IP.
Q. What is gateway.docker.internal?
An older Desktop name for the gateway; prefer host.docker.internal.
Q. Does extra_hosts work in Swarm?
Yes, as extra_hosts in the stack file (hosts in docker service create).
Q. Can I set it for all containers?
Set host-gateway-ip in daemon.json, but each container still needs the entry; Compose files are the clean way.
Q. Does network_mode: host work on Mac?
Recent Docker Desktop versions offer it as an opt-in setting; check Docker's docs.
Interview questions
-
Why does localhost not reach the host from a container?
Model answer: Each container has its own network namespace and loopback.
-
How do you reach the host on Linux?
Model answer: --add-host host.docker.internal:host-gateway or extra_hosts, or the docker0 IP.
-
What does host-gateway resolve to?
Model answer: The host IP on the default bridge, configurable with host-gateway-ip.
-
When would you use network_mode: host?
Model answer: High-performance networking or many ports, accepting no isolation.
-
Container cannot reach Postgres on the host. Checklist?
Model answer: Name resolution, listen_addresses, pg_hba.conf, firewall, port.
-
How do you fake a DNS name for tests?
Model answer: extra_hosts or --add-host.
Quick quiz
Q1. What is localhost inside a container?
The container itself.
Q2. Which value maps to the host IP on Linux?
host-gateway.
Q3. Compose key for /etc/hosts entries?
extra_hosts.
Q4. Top cause of refused connections to the host?
Host app bound to 127.0.0.1.
Q5. Downside of network_mode: host?
No network isolation; Linux only.
Samajla ka?
Revision card
Interview card