Introduction
We will cover How To Connect To LocalHost From Inside A Docker Container.
Did you know that localhost inside a docker container simply refers to the container itself?
I will break this review in the following sections:
- The problem of why localhost is the container and not the host
- Show an example use-case of the problem
- Demonstrate a solution with a bonus tip to make hostnames easier
I have been using this setup successfully in many of my docker deployments and works like a charm letting me deploy wherever I want without having to maintain multiple files or disorganize my source libraries.
I’m going to keep all the steps simple and straight to the point to avoid you have to spend endless hours debugging or trying to figure out why things aren’t working.
The Problem
The problem is that every docker container is basically a mini version of your computer like your host operating system where everything is running in.
In a similar way localhost is reserved specifically for this mini computer (container in this case) so if you try to use it you will simply be referring to the running container.
Rest assured the docker platform has made this easy to resolve we will talk about it in the next section.
Example Of Problem
Let’s see a quick example of the problem and why this may not work for you.
We will use the below simply ubuntu dev docker container file for testing through out this.
# use latest ubuntu image FROM ubuntu:latest # update local repo RUN apt update RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1000 ubuntu RUN echo 'ubuntu:ubuntu' | chpasswd # local time RUN ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime # install deps RUN apt install -y sudo curl vim ssh net-tools netcat git build-essential ip-utils RUN echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers EXPOSE 22 RUN service ssh start
This simply starts up a development Ubuntu docker image. Let’s go ahead and build it and run and see what happens when we ping localhost.
$ docker build -t ubuntu-dev -f ~/code/docker/ubuntu-dev/Dockerfile . Building ubuntu-dev docker container docker build -t ubuntu-dev -f ~/code/docker/ubuntu-dev/Dockerfile . [+] Building 271.5s (12/12) FINISHED => [internal] load build definition from Dockerfile 1.0s => => transferring dockerfile: 499B 0.0s => [internal] load .dockerignore 0.7s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/ubuntu:latest 3.3s => [1/8] FROM docker.io/library/ubuntu:latest@sha256:669e010b58baf5beb2836b253c1fd5768333f0d1dbcb834f7c07a4dc93f474be 4.7s => => resolve docker.io/library/ubuntu:latest@sha256:669e010b58baf5beb2836b253c1fd5768333f0d1dbcb834f7c07a4dc93f474be 0.2s => => sha256:669e010b58baf5beb2836b253c1fd5768333f0d1dbcb834f7c07a4dc93f474be 1.42kB / 1.42kB 0.0s => => sha256:13572ca2bcc043618c2c2ab6ee7dda8ede63ff782a604f58e69c6e7dac62b626 529B / 529B 0.0s => => sha256:a457a74c9aaabc62ddc119d2fb03ba6f58fa299bf766bd2411c159142b972c1d 1.48kB / 1.48kB 0.0s => => sha256:bbf2fb66fa6e06dd46eb26f518f93171ee7c48be68aafb213aa7c2c12f4018ca 27.17MB / 27.17MB 3.0s => => extracting sha256:bbf2fb66fa6e06dd46eb26f518f93171ee7c48be68aafb213aa7c2c12f4018ca 0.6s => [2/8] RUN apt update 4.4s => [3/8] RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1000 ubuntu 1.9s => [4/8] RUN echo 'ubuntu:ubuntu' | chpasswd 1.3s => [5/8] RUN ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime 1.2s => [6/8] RUN apt install -y sudo curl vim ssh net-tools netcat git build-essential 247.4s => [7/8] RUN echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 1.5s => [8/8] RUN service ssh start 1.3s => exporting to image 2.4s => => exporting layers 2.3s => => writing image sha256:f0e814fd628201851620c6dc5432c904941b510df433b72e433b4ce40527cb15 0.0s => => naming to docker.io/library/ubuntu-dev
And running it to get a shell:
$ docker run -d -P -ti ubuntu-yarn 608a78af9770440f77c78976851af3158fb879623bd5915f42ce95302a1b0fd8 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 608a78af9770 ubuntu-dev "bash" 5 seconds ago Up 4 seconds 0.0.0.0:55006->22/tcp admiring_kirch $ docker exec -ti 608a78af9770 bash root@608a78af9770:/#
Now let’s go ahead and ping and see what response we get:
root@608a78af9770:/# ping -c 3 localhost PING localhost (unbiased-coder.com) 56(84) bytes of data. 64 bytes from localhost (unbiased-coder.com): icmp_seq=1 ttl=64 time=0.088 ms 64 bytes from localhost (unbiased-coder.com): icmp_seq=2 ttl=64 time=0.148 ms 64 bytes from localhost (unbiased-coder.com): icmp_seq=3 ttl=64 time=0.171 ms --- localhost ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2062ms rtt min/avg/max/mdev = 0.088/0.135/0.171/0.035 ms
As you can tell docker is basically resolving localhost as the container itself and not our host operating system which we want to connect too.
How To Connect To LocalHost From Inside A Docker Container
Root Cause Of Problem
As described earlier the root cause is that networking requires each system to be identified in a unique way in a virtual network that the docker service creates to identify each container. As such the localhost keyword is reserved locally for each machine and not your host operating system so it’s not accessible using this keywork.
Docker Access Localhost
Accessing docker from localhost alone meaning going from your native operating system to your guest shouldn’t present any issues. You basically want to make sure you are inside a docker container and your /etc/hosts file contains the localhost entries. Typically localhost from docker will be mapped into 127.0.0.1 or 127.0.1.1. If that’s the case in your system then you are good.
It must be noted that the above would work if your guest is a Linux based distribution. If it’s a MAC or Windows the way you can access those interfaces is rather different. For Mac the process is very similar you can run the ifconfig command and on Windows the ipconfig command. If you see in the output result of those the IP of 127.0.0.1 then docker should be accessible from localhost.
If none of the above works then look at the solution below which makes use for a host docker internal hostname that is predefined by docker allowing you to perform a similar thing.
To make things simpler I created a diagram that shows the blocks of how Docker looks at the native OS with accessing the guests, that will also illustrate the conflict of docker access in localhost.
A few observations of the docker layout:
- The first layer is where the native operating system is this could be Mac, Windows or Linux
- Docker is a middleware between the native operating system and the guests. Basically this is what does the mapping on connecting to docker from localhost and to guest docker containers referencing localhost
- In the bottom layer you have the actual guest docker containers which really have the network subnet mapping but can also refer to the native operating system as localhost if the hosts file is correct.
How To Reach Localhost From Docker Container
You can try to access it using docker run with the following example:
$ docker run -p 9911:443 docker_image
What this does is start your docker image binding on local port 9911 and it will forward to the containers port 443 (https). The simplistic case of this is by default bonded into localhost. The traffic is redirected automatically by your host operating system inside the guest docker container.
Solution
Now lets move on to the solution which in this case is pretty simple. You have the option to use a specially created hostname that’s done automatically by you by the docker service and it’s called host.docker.internal.
This special hostname basically resolves to your host machine be it a Mac, Windows or Linux machine.
To Test our theory we will perform two tests to see if both succeed:
- First ping the host machine to see if it’s responding to pings (however this will not verify for sure it’s our host machine so we need test 2 below)
- Connect to some service running on the host machine to make sure this is indeed our host operating system (in my case a MAC)
Test To See If Ping Works
Let’s start with the easy test to verify the theory that we at least can ping it.
root@608a78af9770:/# ping -c 3 host.docker.internal PING host.docker.internal (192.168.65.2) 56(84) bytes of data. 64 bytes from 192.168.65.2 (192.168.65.2): icmp_seq=1 ttl=37 time=1.64 ms 64 bytes from 192.168.65.2 (192.168.65.2): icmp_seq=2 ttl=37 time=1.63 ms 64 bytes from 192.168.65.2 (192.168.65.2): icmp_seq=3 ttl=37 time=1.44 ms --- host.docker.internal ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2009ms rtt min/avg/max/mdev = 1.442/1.571/1.640/0.091 ms
As you can tell we can quickly verify this is working and we got a response. We also see that docker internally has allocated the IP of 192.168.65.2 for our host machine (not that it matters in this case).
As a side note here you may see that the host is not responding to a PING. The reason for this can also be some firewall rule inside the docker container itself. If this happens try to use the shell connection trick we mentioned earlier to connect to the instance and flush the firewall rules using the following command:
# iptables -F
Once this is executed without rebooting your docker container now try again to run the ping command. If this works it means there was a firewalling preventing you from connecting to localhost from a docker container. As a closing note do not forget to restore your firewall rules or just simply reboot the docker instance as it will be left unprotected since we flushed all the firewall rules above.
Test To See If We Can Connect To A Service In Host Machine
So now that we did this test going lets try to connect to some test service in our host machine and see if it’s going to yield any results. In order to do this we will need the help of lsof which is a tool basically to help you identify open file descriptors in a Unix based operating system.
Since our MAC is Unix based it will work, particularly we want to find an open port so in this case we want to look only for file descriptors that have open ports that are IPv4. I have also written an article which you can find at the conclusion below describing this command in detail if you would like to learn more about lsof.
user@host-mac-machine $ lsof -i4|grep LISTEN .... vpnkit-br 28599 alex 8u IPv4 0xd22e3d7f6b9574f3 0t0 TCP *:63073 (LISTEN) ....
I abstracted the output for security reasons so not all my running services are exposed but you can pick any service in your host machine that has the LISTEN state and it’s binded in *. Make sure it’s not in localhost as this will not be connectable by the docker instance since it’s running exclusively on the localhost interface of the host machine.
Now that we have a running port and we know what we want to do let’s proceed and do a test and see if this is working.
root@608a78af9770:/# nc -vv host.docker.internal 63073 Connection to host.docker.internal 63073 port [tcp/*] succeeded!
Switching back to our docker container now we can see that we can successfully connect to the open port of our host MAC service. Do note that we are using the netcat command to test the connectivity which just let’s us perform a simple TCP/IP connection to the port we specify in our command line. If you would like to learn more about the netcat command you can visit the documentation here.
Bonus – Use Docker Names To Identify Hosts
One thing worth mentioning is that in your docker files or in your build process you have the option to uniquely identify your docker instances with specific hostnames. While this is very useful sometimes it’s omitted and not used by most people which results into auto-naming or using default names passed in the command line.
The easiest way to do this is to specify the hostname flag while you are starting up your docker instance.
An example of this can be seen below:
$ docker run --hostname myubuntu -d -P -ti ubuntu-dev edaf4495043a71520f888d71b9c9fd93d9b017f985903c93a4d0c4517c106c17 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES edaf4495043a ubuntu-dev "bash" 4 seconds ago Up 2 seconds 0.0.0.0:55008->22/tcp romantic_lamarr $ docker exec -ti edaf4495043a bash root@myubuntu:/# cat /etc/hostname myubuntu root@myubuntu:/# cat /etc/hosts unbiased-coder.com localhost 172.17.0.2 myubuntu
As you can see above we specified a hostname called myubuntu. To verify this we start the docker container and connect to it. Immediately the prompt is now changed to myubuntu and the entry exists in both the hosts file and the hostname.
Using this tip you can easily identify and ping your docker containers without a problem outside context and always know what to expect between them.
Conclusion
We were able to successfully show how you can access your host operating system from inside a docker container.
If you found the How To Connect To LocalHost From Inside A Docker Container useful and you think it may have helped you please drop me a cheer below I would appreciate it.
If you have any questions, comments please post them below or send me a note on my twitter. I check periodically and try to answer them in the priority they come in. Also if you have any corrections please do let me know and I’ll update the article with new updates or mistakes I did.
Do you use docker names to easily identify your docker containers?
I always do as it makes accessing services very easy when dealing with multiple instances such as databases, redis and web servers.
If you would like to learn more about other Linux and system setup stuff you can check the articles below: