__biancatcatcat

Two simple, quick methods to access the host network from a Docker container (updated for Docker 18.03, Linux & macOS)

Last updated: April 6, 2019

Last tested with version Docker version 18.09.3 on Ubuntu 16.04, Debian 9 (stretch), macOS Mojave (10.14.4).

NOTE: If your problem is connecting TO containers FROM a macOS host, see this post: https://biancatamayo.me/blog/docker-troubleshooting-macos-network/


Sometimes you need to be able to connect to the host network from inside a Docker container. Could be for debugging, or small projects, or whatever reason. I ran into this need and after googling, it took me way too long to eventually find the answer in the docs. If you're in the same situation, I hope I can save you some time!

Update April 2019: This won't work for Kubernetes pods/services. Kubernetes networking works differently from plain Docker networking.

Depending on your use case, a network of type host may not work (and requires some setup). Docker provides a way to add hosts into your container's /etc/hosts file. I'll show it here in two ways, one via CLI and one via docker-compose as a bonus.

1. CLI: Using the --add-host parameter with docker run

If you're on macOS using Docker Desktop, it's easier. Scroll down or skip to note for macOS.

Main idea: pass in the docker0 IP address to the container via --add-host:

Get the host machine's address for the docker0 interface and put it in shell var (note that docker0 doesn't exist on macOS, scroll to the next part if you're on a Mac). Of course, this part is optional if you just want to paste the IP in.

We put it in the HOST_IP shell var like so:

$ HOST_IP=`ip -4 addr show scope global dev docker0 | grep inet | awk '{print \$2}' | cut -d / -f 1`

(To find the IP address without using the above snippet, you can use the command ifconfig docker0).

Then we execute docker run with --add-host, using the variable we set, e.g.:

$ docker run --add-host outside:$HOST_IP --name busybox -it busybox /bin/sh

Now, within the container, we can inspect /etc/hosts, we see an extra line has been added:

bcat@remote:~$ docker run --add-host outside:$HOST_IP --name busybox -it busybox /bin/sh

## now we're in the container:

/ $ cat /etc/hosts
127.0.0.1    localhost  
::1    localhost ip6-localhost ip6-loopback
fe00::0    ip6-localnet  
ff00::0    ip6-mcastprefix  
ff02::1    ip6-allnodes  
ff02::2    ip6-allrouters  
172.17.0.1    outside <---- THIS ONE!  
172.17.0.3    a8300156a695  

The IP address 172.17.0.1 which we passed in via docker run is now resolvable via the hostname "outside"!

We can verify it:

/ $ ping outside
PING outside (172.17.0.1): 56 data bytes  
64 bytes from 172.17.0.1: seq=0 ttl=64 time=0.326 ms  
64 bytes from 172.17.0.1: seq=1 ttl=64 time=0.118 ms  
64 bytes from 172.17.0.1: seq=2 ttl=64 time=0.098 ms  
64 bytes from 172.17.0.1: seq=3 ttl=64 time=0.137 ms  
^C
--- outside ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss  
round-trip min/avg/max = 0.098/0.169/0.326 ms

note for macOS (and Windows):

This supposedly also works on Windows, but as I don't have a Windows dev machine, I can't test it.

Docker for Mac does not have the docker0 interface1 that's used in the above example. Instead, you can actually simply go into the container and use a special macOS-only DNS name (docker.for.mac.localhost) that comes out of the box (version 17.06+):

Update April 2019: The DNS name is now called host.docker.internal in the docs, though docker.for.mac.localhost works just as well for now in Docker 18.09.3. Additionally, you can also now also use gateway.docker.internal to access the host's gateway.

# In my macOS:

$ bianca@burritomac:~$ docker run -it busybox /bin/sh


## now we're in the container!:

## pinging docker.for.mac.localhost
/ $ ping docker.for.mac.localhost
PING docker.for.mac.localhost (192.168.65.2): 56 data bytes  
64 bytes from 192.168.65.2: seq=0 ttl=37 time=0.420 ms  
64 bytes from 192.168.65.2: seq=1 ttl=37 time=0.564 ms  
^C
--- docker.for.mac.localhost ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss  
round-trip min/avg/max = 0.420/0.492/0.564 ms


## pinging host.docker.internal

/ $ ping host.docker.internal
PING host.docker.internal (192.168.65.2): 56 data bytes  
64 bytes from 192.168.65.2: seq=0 ttl=37 time=0.289 ms  
^C
--- host.docker.internal ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss  
round-trip min/avg/max = 0.289/0.289/0.289 ms


## pinging the host's gateway:

/ $ ping gateway.docker.internal
PING gateway.docker.internal (192.168.65.1): 56 data bytes  
64 bytes from 192.168.65.1: seq=0 ttl=37 time=0.265 ms  
64 bytes from 192.168.65.1: seq=1 ttl=37 time=0.625 ms  
^C
--- gateway.docker.internal ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss  
round-trip min/avg/max = 0.265/0.445/0.625 ms  

Easy. 🍰

From the docs:

The host has a changing IP address (or none if you have no network access). From 18.03 onwards our recommendation is to connect to the special DNS name host.docker.internal, which resolves to the internal IP address used by the host. This is for development purpose and will not work in a production environment outside of Docker Desktop for Mac.

The gateway is also reachable as gateway.docker.internal. [src]

2. docker-compose:

This mechanism also available docker-compose.yml files as the extra_hosts key, like so:

docker-compose.yml:

version: '2'  
services:  
  samplev2:
    image: "redis:alpine"
    extra_hosts:
       - "outside:172.17.0.1"

And since we took advantage of variable substitution in CLI example, I'll show how we can do the same here:

version: '2'  
services:  
  samplev2:
    image: "redis:alpine"
    extra_hosts:
       - "outside:${HOST_IP}"

In this example, we don't have an .env file, and HOST_IP is only a shell variable. To become an env var, we need export it first, and then run docker-compose:

$ bcat@remote:~$ export HOST_IP=$HOST_IP && docker-compose up -d

Note: I don't run it as sudo here, but remember that sudo won't work with export!

The -d flag runs it in the background, and we can go into the container and check out our /etc/hosts file in almost the same way as we did earlier. Since we didn't name the container, we grab the container ID using docker ps first, which is 0dde138913ee:

bcat@remote:~$ docker exec -it 0dde138913ee /bin/sh

/# cat /etc/hosts
127.0.0.1    localhost  
::1    localhost ip6-localhost ip6-loopback
fe00::0    ip6-localnet  
ff00::0    ip6-mcastprefix  
ff02::1    ip6-allnodes  
ff02::2    ip6-allrouters  
172.17.0.1    outside <---- IT'S HERE!  
172.21.0.2    0dde138913ee  

Success!

Anyway, hope this is helpful in some way. And if it is please let me know so I can write more! You can always find me on Twitter. :)

Update December 2019: I understand there's a desire for the convenience hostname host.docker.internal to be made available on all platforms, and not just macOS or Windows. I don't have an opinion on that, but if you do you can follow the lengthy discussion here: https://github.com/docker/for-linux/issues/264

Again, if your problem is connecting TO containers FROM a macOS host, see this post: https://biancatamayo.me/blog/docker-troubleshooting-macos-network/

Footnotes and such:

What would you like to see me write about? Comments and questions are welcome in the comments or on Twitter!