Connect to a Service Running on Your Docker Host from a Container
This could be useful if you want to connect to a database or something else not running in Docker.
I’ve written about this topic back in 2017 and 2018 but a few things have changed since then to make this process easier no matter how you run Docker.
Here’s 2 use cases of when you might want to do this:
Maybe you’re transitioning into using Docker and you’re not ready to move your database into Docker yet so you want to connect to a locally running version of PostgreSQL, MySQL or whatever DB you have.
Or, perhaps you have a few services that you’re in the process of Dockerizing. 2 of them are in Docker but the 3rd service isn’t running in Docker yet but your other 2 services need to connect to this 3rd service.
# Modern Versions of Docker
All of the options below let you use Docker’s bridge network without having to
resort to using --net=host
.
We’re going to take advantage of using host.docker.internal
which is a
special DNS name available in your container that resolves to your Docker
host’s local bridge network gateway IP address. We’ll cover that in more detail
once we see it in action by running a few commands.
For example, if you wanted to connect to Postgres running on port 5432 you can
configure your Dockerized app to connect to your local Postgres service on
host.docker.internal:5432
.
Docker Desktop
If you’re using Docker Desktop that hostname works out of the box.
The above feature isn’t new. A few sources indicate that’s been available since Docker Engine 18.03 which was released in March 2018. Chances are you have that!
Native Linux in Production
If you’re not using Docker Desktop, such as native Linux in production you have a feature that’s available in Docker Engine 20.10+ (released in December 2020).
It depends on using host-gateway
which is a special value that you can use
with --add-host
to get your Docker bridge network’s gateway IP address. The
basic idea here is you can use that combined with adding the
host.docker.internal
hostname to make things work the same as Docker Desktop.
Not using Docker Compose?
If you’re running your container directly with the docker
CLI you can add
this flag --add-host="host.docker.internal:host-gateway"
to your app’s
container command.
Using Docker Compose?
In your app’s service you can set this property extra_hosts: ["host.docker.internal:host-gateway"]
which does the same thing as above.
# Trying It Out with Ping
The above goes over how it all works but some of the details are a little abstract, especially with terms like bridge networks and gateway IP addresses.
Try running this container:
docker container run --rm \
--add-host="host.docker.internal:host-gateway" \
debian:stable-slim \
bash -c "apt-get update && apt-get install -y iputils-ping && ping -c 3 host.docker.internal"
No matter how you’re running Docker (Docker Desktop or native) you should see this:
PING host.docker.internal (172.17.0.1) 56(84) bytes of data.
64 bytes from host.docker.internal (172.17.0.1): icmp_seq=1 ttl=64 time=0.039 ms
64 bytes from host.docker.internal (172.17.0.1): icmp_seq=2 ttl=64 time=0.054 ms
64 bytes from host.docker.internal (172.17.0.1): icmp_seq=3 ttl=64 time=0.038 ms
--- host.docker.internal ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2105ms
rtt min/avg/max/mdev = 0.038/0.043/0.054/0.007 ms
But, if you’re not using Docker Desktop and you tried to run the above without
the --add-host
flag then you’d get this error ping: host.docker.internal: Name or service not known
.
That’s because --add-host
will add this entry to your container’s
/etc/hosts
file, such as 172.17.0.1 host.docker.internal
. Without that
flag then host.docker.internal
can’t be looked up and it fails.
By the way, that 172.17.0.1
IP address is what’s associated
with the output of running ip a
or ifconfig
for your
docker0
network interface, your IP address may be different.
That’s the bridge network’s gateway IP address and is what
host.docker.internal
maps back to.
You can directly ping or access that IP address instead, even without using
--add-host
but using DNS is nice here to avoid needing to remember that IP
address and on some systems it might not be that IP address depending on how
your network is set up.
You can dive 1 level deeper and run docker network inspect bridge
to see the
output. About a third of the way down you’ll see "Gateway": "172.17.0.1"
.
With that said, that leads us into how you used to be able to connect to your Docker host before these modern features were available from Docker.
# Ancient Versions of Docker
If you’re locked into a really old version of Docker you have a couple of options.
You can run docker network inspect bridge -f "{{range .IPAM.Config}}{{.Gateway}}{{end}}"
to get the gateway IP address by itself.
We’re using Go’s templating system to parse the IP address.
Instead of using host.docker.internal
you can access that IP directly. On my
machine it returns 172.17.0.1
but it’s not guaranteed to be that on your
system, it depends on how your network set up.
I would use the above solution since it doesn’t require external dependencies
or custom parsing any output to get the IP address but alternatively you can
run ip a
or your operating system’s comparable command (ifconfig
, etc.) to
find the docker0
network interface.
On my machine ip a
includes this output:
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:f0:24:c9:09 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:f0ff:fe24:c909/64 scope link
valid_lft forever preferred_lft forever
On line 3 you can see the same 172.17.0.1
IP address. This post from
2018 goes into parsing that output in case you need to script out a solution to
grab the IP address.
The demo video below covers everything in this post and running some of the commands.
# Demo Video
Timestamps
- 1:04 – Going over a few common use cases
- 1:56 – What is a modern version of Docker?
- 2:38 – What is host.docker.internal?
- 3:18 – Getting this to work with and without Docker Desktop
- 3:53 – What is host-gateway?
- 4:17 – Using host-gateway with the Docker CLI and Docker Compose
- 6:24 – Demoing things out with ping
- 7:51 – Showing what happens without adding host.docker.internal
- 8:39 – Network interfaces, gateway IP addresses and DNS
- 10:04 – Inspecting Docker’s bridge network
- 10:45 – Stuck on an ancient version of Docker?
When was the last time you’ve had to do this? Let us know below.