A process inside a container needs to reach a service running directly on the Docker host, for example a database listening on the host's port 5432. Explain the special DNS name host.docker.internal: what it resolves to, where it works out of the box, and what you do on a platform where it does not exist.
answer
- container 127.0.0.1 = container's own loopback
- Desktop: name is automatic; Linux: --add-host host-gateway
- host-gateway token resolves to the bridge gateway address
- host service must bind 0.0.0.0, not loopback
- dev convenience, not production wiring
basics
~20 shost.docker.internal is a name Docker injects that resolves to the host from inside a container. It exists automatically on Docker Desktop (macOS/Windows). On plain Linux you must add it yourself with --add-host=host.docker.internal:host-gateway, and the host service must listen on an address the container can reach, not only 127.0.0.1.
solid answer
~50 s`host.docker.internal` is a magic hostname Docker resolves to the host machine, so container code can reach services running outside containers. Inside a container, `127.0.0.1` is the container's own loopback, not the host, which is why a literal localhost connection fails. On Docker Desktop for macOS and Windows the name is provided automatically because the host is a separate VM. On Linux there is no built-in entry: you add one with `--add-host=host.docker.internal:host-gateway` (Compose: `extra_hosts`), where `host-gateway` is a Docker-resolved token for the bridge gateway address, usually 172.17.0.1. Two caveats. First, the host process must be bound to 0.0.0.0 or to the bridge gateway; a service bound only to 127.0.0.1 is unreachable from containers. Second, host firewall rules apply to traffic arriving on the bridge interface. For production I would not depend on it — it is a development convenience. Real dependencies should be containers on a shared network or reached by a routable address.
code
bash · 2 linesdocker run --rm -it --add-host=host.docker.internal:host-gateway alpine \
sh -c 'getent hosts host.docker.internal; nc -vz host.docker.internal 5432'go deeper
Know that localhost inside a container is the container itself, and that host.docker.internal is the name to use on Docker Desktop.
Explain the Linux gap and the --add-host=host.docker.internal:host-gateway fix, plus the bind-address requirement on the host service.
Add the firewall and per-network gateway nuances, and argue it is a development affordance that should be configuration-driven rather than baked into code.
Treat it as a smell in production topology: dependencies reached via 'the host' do not survive multi-host scheduling, so push them into the network model or real DNS.
## Why localhost does not work Every container gets its own network namespace, which means its own loopback device. When code in a container connects to 127.0.0.1 it reaches services inside that same container, never the host and never another container. This is the single most common Docker networking surprise for people porting a local development setup. ## What the name is `host.docker.internal` is a name that Docker arranges to resolve to the host. On Docker Desktop (macOS, Windows) containers actually run inside a Linux VM, so the host is a separate machine reachable over a virtual network; Docker Desktop publishes this name (and the alias `gateway.docker.internal` for the gateway) automatically. Code can then use `host.docker.internal:5432` where it would have used `localhost:5432`. ## Linux behaviour On native Linux the daemon does not inject the name by default. The supported way to get it is: `docker run --add-host=host.docker.internal:host-gateway ...` `host-gateway` is a special token the daemon substitutes with the host address on the container's bridge — typically 172.17.0.1 for the default bridge, or the gateway of whichever user-defined network the container is on. The result is a plain `/etc/hosts` entry inside the container. In Compose the equivalent is an `extra_hosts` entry with the same `host.docker.internal:host-gateway` value. The alternatives on Linux are to hardcode the gateway IP, which is brittle because it differs per network, or to use `--network host`, which removes the container's network isolation altogether and is a heavy hammer for the problem. ## The two things that still break it **Bind address.** A host service listening on 127.0.0.1 only accepts connections arriving on the host loopback. Traffic from a container arrives on the bridge interface from a 172.x source, so it is refused. The host process must bind 0.0.0.0, or the bridge gateway address specifically. Many distro packages (Postgres, MySQL, dev servers) default to loopback-only for good reasons — changing that on a shared or public machine deliberately widens exposure, so it belongs to development environments. **Host firewall.** Rules in the INPUT chain apply to packets from containers arriving at the host, so a restrictive firewall must allow the bridge subnet on that port. Failure here looks like a hang or timeout rather than a refusal. ## When not to use it It is a development affordance. Its presence differs per platform, it hardcodes the assumption that a dependency lives outside the container world, and in an orchestrated environment there is no meaningful single host to point at. Production dependencies should be containers on a shared user-defined network addressed by name, or external services addressed by a real DNS name. A useful compromise is a configurable environment variable for the host, defaulted to `host.docker.internal` in the development compose file and to a service name elsewhere — the application code then does not encode the difference. ## Related debugging moves To verify the path from inside a container: `getent hosts host.docker.internal` to check resolution, then a TCP probe such as `nc -vz host.docker.internal 5432`. Resolution succeeding but the connection being refused points at the bind address; a timeout points at the firewall.
- Resolution works but the connection is refused. What is the most likely cause?The host service is bound to 127.0.0.1 only. Traffic from a container arrives on the bridge interface with a 172.x source address, so a loopback-only listener rejects it. Rebind the service to 0.0.0.0 or to the bridge gateway address in development. A timeout instead of a refusal would point at the host firewall rather than the bind address.
saying these in an interview costs you the question
- Saying 127.0.0.1 inside a container reaches the host
- Assuming host.docker.internal exists by default on Linux
- Hardcoding 172.17.0.1 and expecting it to hold on user-defined networks
- Reaching for --network host as the normal fix
- Forgetting the host process must not be bound to loopback only