59

I have a daemon on my host running on some port (i.e. 8008) and my code normally interacts with the daemon by contacting localhost:8008 for instance.

I've now containerized my code but not yet the daemon. How can I forward the localhost:8008 on my container to localhost:8008 on the host running the container (and therefore the daemon as well).

The following is netstat -tlnp on my host. I'd like the container to forward localhost:2009 to localhost:2009 on the host

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name            
tcp        0      0 127.0.0.1:2009          0.0.0.0:*               LISTEN      22547/ssh       
tcp6       0      0 :::22                   :::*                    LISTEN      -               
tcp6       0      0 ::1:2009                :::*                    LISTEN      22547/ssh            
Setheron
  • 3,520
  • 3
  • 34
  • 52

7 Answers7

49

So the way you need to think about this is that Docker containers have their own network stack (unless you explicitly tell it to share the host's stack with --net=host). This means ports need to be exposed both inside the docker container and also on the outside (documentation), when linked with host ports. The ports exposed on the container need to be bound to the host ports explicitly (with -p xxxx:yyyy in your docker run command) or implicitly (using EXPOSE in your Dockerfile and using -P on the command line), like it says here. If your Dockerfile does not contain EXPOSE 8008, or you do not specify --expose 8008 in your docker run command, your container can't talk to the outside world, even if you then use -p 8008:8008 in your docker run command!

So to get tcp/8008 on the host linked with tcp/8008 on the container, you need EXPOSE 8008 inside your Dockerfile (and then docker build your container) OR --expose 8008 in your docker run command. In addition, you need to either use -P to implicitly or -p 8008:8008 to explicitly link that exposed container port to the host port. An example docker run command to do this might look like:

docker run -it --expose 8008 -p 8008:8008 myContainer

It's handy to remember that in the -p 8008:8008 command line option, the order for this operation is -p HOST_PORT:CONTAINER_PORT. Also, don't forget that you won't be able to SSH into your container from another machine on the internet unless you also have this port unblocked in iptables on the host. I always end up forgetting about that and waste half an hour before I remember I forgot to iptables -A INPUT ... for that specific tcp port on the host machine. But you should be able to SSH from your host into the container without the iptables rule, since it uses loopback for local connections. Good luck!

L0j1k
  • 12,255
  • 7
  • 53
  • 65
  • 1
    So I think I then need another firewall rule to forward it to localhost. – Setheron May 08 '15 at 05:44
  • 1
    I've added a little more information. You only need to add an iptables rule on the host if you're trying to ssh into your container from some other computer on the internet. You should be able to ssh into your container if you are using ssh from the host machine. – L0j1k May 08 '15 at 05:47
  • 1
    So the daemon is only listening on localhost. I can connect to port 8008 from the IP of my host but then I need to forward then to the localhost nic – Setheron May 08 '15 at 05:48
  • So if your host machine is 172.17.1.10, and you have started your docker container on that host (and it's exposed tcp/8008 properly), you can `ssh localhost -p 8008` without needing to change your iptables. BUT if you are trying to have this SSH server listen to requests from the internet, you need to `iptables -A INPUT ...` in order for your host computer to allow traffic to tcp/8008 (which is now connected to the SSH server inside your container). – L0j1k May 08 '15 at 05:51
  • 9
    This answer doesn't work if the container's process has bound to localhost:8008, which I interpreted the original question to ask. – user239558 Sep 08 '16 at 05:05
  • 4
    If your container's process is told to listen only for localhost connections, absolutely *nothing* will be able to talk to your container (unless I believe you use `--net=host` in your `docker run` command as I mention in my answer, which shares the host's networking stack with your container). Docker's networking treats container processes as though they existed in their own entire universe. If you're telling your process to bind to `localhost:8008` for example in your Golang web app, you need to change that to `0.0.0.0:8008` in order for literally anything to be able to talk to it. – L0j1k Sep 08 '16 at 19:02
  • ummm, I thought -p was shorthand for --expose? – Alexander Mills Jun 01 '17 at 17:49
  • I have luck using -p 3051:3050, where 3051 is host port and 3050 is the container port. – Alexander Mills Jun 01 '17 at 17:50
  • @Setheron, can you share if and how you've resolved this issue? – shlomi33 Sep 05 '18 at 06:52
  • Just want to say thank you for your answer - your `--net=host` addendum was exactly what I was looking for when trying to use my host machine's port-forwarded stack. – Try431 Feb 18 '19 at 23:02
  • 1
    --expose seems somewhat ambiguous. This article indicates that --expose is purely documentation and offers no other functionality. So how does the following statement can be correct? "If your Dockerfile does not contain EXPOSE 8008, or you do not specify --expose 8008 in your docker run command, your container can't talk to the outside world, even if you then use -p 8008:8008 in your docker run command!" – LeanMan Dec 16 '20 at 05:16
  • 1
    It seems -P means publish all and using this with --expose or EXPOSE tells docker to publish all exposed ports binding them to respective ephermal ports. -p is different and it explicitly binds a host and container's port. I think the answer here needs a little bit of an update. – LeanMan Dec 16 '20 at 05:31
16

TL;DR: You can use the special hostname host.docker.internal instead of localhost anywhere inside the container that you want to access localhost on the host. Note that:

  • macOS and Windows versions of Docker Desktop have this feature enabled by default.
  • Linux hosts (using Docker v 20.10 and above - since December 14th 2020) require you to add --add-host=host.docker.internal:host-gateway to your Docker command to enable the feature.
  • Docker Compose on Linux requires you to add the following lines to the container definition:
extra_hosts:
- "host.docker.internal:host-gateway"

Full answer: Is the host running MacOS or Windows? Buried in the documentation for Docker Desktop is the fact that there is no docker0 bridge on MacOS and there is no docker0 bridge on Windows. Apparently that's the cause of this. In both cases the workaround (given right after, in a subsection titled "Use cases and workarounds") is to use the special hostname host.docker.internal in placed of localhost anywhere inside the container that you want to access localhost on the host.

If the host is Linux, there are some Linux-only techniques for achieving this. However, host.docker.internal is also useable with a Linux host, but it has to be enabled first. See the Linux part of the TL;DR, above, for instructions.

By this method, in OP's case host.docker.internal:8008 should be used instead of localhost:8008. Note that this is a code or configuration change of the application code running inside the container. There is no need to mention the port in the container configuration. Do not try to use -p or --expose in the docker run commandline. Not only is it not necessary, but your container will fail to start if the host application you want the container to connect to is already listening on that port.

bosconi
  • 190
  • 1
  • 5
  • I always forget this one, was setting up a local confluence data center instance and needed to connect to my host version of postgres...I think that you still may need the port and expose part from the answer above (I left those in at least) but as soon as I entered in host.docker.internal the setup page was good to go. Thanks for the answer! – burnsjeremy Jun 22 '23 at 00:15
  • 1
    @burnsjeremy the `-p` flag to `docker run` (short for `--publish`) is definitely not necessary when you want a process inside of a container to be able to communicate with a process listening on a port on the host. `-p` should only be used when you will have a process inside of the container listening on a port, and need packets from the host to be routed to that port. Half of the answers to this question conflate these two scenarios (which are the opposite of one another) and don't actually answer what OP is asking about. – bosconi Jun 23 '23 at 19:44
4

After checked the answers and did some investigation, I believe there are 2 ways of doing that and these 2 only work in Linux environment.

The first is in this post How to access host port from docker container

The second should be set your --network=host when you docker run or docker container create. In this case, your docker will use the same network interface you use in Mac.

However, both ways above cannot be used in Mac, so I think it is not possible to forward from the container to host in Mac environment. Correct me if I am wrong.

StevenR
  • 702
  • 1
  • 8
  • 17
  • It is possible on a Mac ... the way to do it is just different than with a Linux host. Docker on Mac and Windows both offer `host.docker.internal` as a special hostname to use inside the container, which will forward to `localhost` on the host OS (macOS or Windows). See my answer for details. What you describe is correct for Linux hosts. – bosconi Jul 15 '22 at 02:24
1

If you're doing this on your local machine, you can simple specify the network type as host when starting your container (--network host), which will make your host machine share network with your docker container.

eg:

Start your container:

docker run -it --rm --network host <container>

On your host machine, Run:

python3 -m http.server 8822

Now from your container run:

curl 127.0.0.1:8822

If all went well you'll see traffic on your host terminal.

Serving HTTP on 0.0.0.0 port 8822 (http://0.0.0.0:8822/) ...
127.0.0.1 - - [24/Jan/2023 22:37:01] "GET / HTTP/1.1" 200 -
chawila
  • 412
  • 1
  • 4
  • 22
-1

I'm not sure if you can do that just with docker's settings. If my under standing is correct, expose port is not what you looking for. Instead, establish ssh port forwarding from container to host mightbe the answer.

Andy
  • 39
  • 4
  • This is exactly what I want to do. But can it be done as a Docker option or set in the Dockerfile? – k-den May 12 '20 at 20:57
-1

You can easily use -p 127.0.0.1:8008:8008 and forward the container's port 8008 to localhost's port 8008. An example docker command would be:

docker run -it -p 127.0.0.1:8008:8008 <image name>
Community
  • 1
  • 1
-6

docker run -d --name <NAME OF YOUR CONTAINER> -p 8008:8008 <YOUR IMAGE>

That will publish port 8008 in your container to 8008 on your host.

Matt Barlow
  • 165
  • 4
  • 2
    It's saying address already in use? I want localhost:8008 on my container to forward to localhost:8008 on the host – Setheron May 07 '15 at 19:23
  • Maybe something is already listening on port `8008` on your host. You can check by typing `netstat -tlnp` if the host is a Linux host. If there is something already listening on port `8008` on your host you will need to stop it first. – Matt Barlow May 07 '15 at 19:26