Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to expose a docker container port bound to 127.0.0.1 to host?

I run a service inside a container that binds to 127.0.0.1:8888.
I want to expose this port to the host.
Does docker-compose support this?

I tried the following in docker-compose.yml but did not work.

expose: 
  - "8888"
ports:
  - "8888:8888"

P.S. Binding the service to 0.0.0.0 inside the container is not possible in my case.


UPDATE: Providing a simple example:

docker-compose.yml

version: '3'
services:  
  myservice:
    expose: 
      - "8888"
    ports:
      - "8888:8888"
    build: .

Dockerfile

FROM centos:7
RUN yum install -y nmap-ncat
CMD ["nc", "-l", "-k", "localhost", "8888"]

Commands:

$> docker-compose up --build
$> # Starting test1_myservice_1 ... done
$> # Attaching to test1_myservice_1

$> nc -v -v localhost 8888
$> # Connection to localhost 8888 port [tcp/*] succeeded!
TEST
$>

After inputing TEST in the console the connection is closed, which means the port is not really exposed, despite the initial success message. The same issue occurs with with my real service.
But If I bind to 0.0.0.0 (instead of localhost) inside the container everything works fine.

like image 351
Marinos An Avatar asked Sep 26 '18 08:09

Marinos An


People also ask

How do I expose a docker port?

Exposing Docker ports can be done using the ‘-p’ option with ‘docker run’ command to bind the port when launching the container: docker run -d -p 9090:80 -t nginx This command will create a container with the image ‘nginx’ and bind the container’s port 80 to the host machine’s port 9090.

How to bind Docker container port 80 to localhost?

To bind the Docker container port 80 to the host system port 8000 and IP address 127.0.0.1 (a.k.a. localhost), just run the following command: Docker port binding is an important concept to understand if you’re working with containers. It can be confusing at first and raise questions on why you need to configure incoming connections.

How to map port 10883 to 1883 in dockerfile?

When you start the container using the docker run command you get to decide what port on the host machine is mapped to that port on the container. This will expose port 10883 on the host and map it to 1883 on the container. "Expose" in dockerfile is kind of a meta data which tells which port you should work with.

What is the difference between expose and expose in Docker?

The above line will instruct Docker that the container’s service can be connected to via port 8080. By default, the EXPOSE keyword specifies that the port listens on TCP protocol. On the other hand, –expose is a runtime flag that lets you expose a specific port or a range of ports inside the container.


2 Answers

Typically the answer is no, and in almost every situation, you should reconfigure your application to listen on 0.0.0.0. Any attempt to avoid changing the app to listen on all interfaces inside the container should be viewed as a hack that is adding technical debt to your project.


To expand on my comment, each container by default runs in its own network namespace. The loopback interface inside a container is separate from the loopback interface on the host and in other containers. So if you listen on 127.0.0.1 inside a container, anything outside of that network namespace cannot access the port. It's not unlike listening on loopback on your VM and trying to connect from another VM to that port, Linux doesn't let you connect.

There are a few workarounds:

  1. You can hack up the iptables to forward connections, but I'd personally avoid this. Docker is heavily based on automated changes to the iptables rules so your risk conflicting with that automation or getting broken the next time the container is recreated.
  2. You can setup a proxy inside your container that listens on all interfaces and forwards to the loopback interface. Something like nginx would work.
  3. You can get things in the same network namespace.

That last one has two ways to implement. Between containers, you can run a container in the network namespace of another container. This is often done for debugging the network, and is also how pods work in kubernetes. Here's an example of running a second container:

$ docker run -it --rm --net container:$(docker ps -lq) nicolaka/netshoot /bin/sh
/ # ss -lnt
State       Recv-Q Send-Q        Local Address:Port    Peer Address:Port
LISTEN      0      10                127.0.0.1:8888                  *:*
LISTEN      0      128             127.0.0.11:41469                  *:*
/ # nc -v -v localhost 8888
Connection to localhost 8888 port [tcp/8888] succeeded!
TEST
/ #

Note the --net container:... (I used docker ps -lq to get the last started container id in my lab). This makes the two separate containers run in the same namespace.

If you needed to access this from outside of docker, you can remove the network namespacing, and attach the container directly to the host network. For a one-off container, this can be done with

docker run --net host ...

In compose, this would look like:

version: '3'
services:  
  myservice:
    network_mode: "host"
    build: .

You can see the docker compose documentation on this option here. This is not supported in swarm mode, and you do not publish ports in this mode since you would be trying to publish the port between the same network namespaces.

Side note, expose is not needed for any of this. It is only there for documentation, and some automated tooling, but otherwise does not impact container-to-container networking, nor does it impact the ability to publish a specific port.

like image 98
BMitch Avatar answered Oct 06 '22 11:10

BMitch


According @BMitch voted answer "it is not possible to externally access this port directly if the container runs with it's own network namespace".

Based on this I think it worths it to provide my workaround on the issue:

One way would be to setup an iptables rule inside the container, for port redirection, before running the service. However this seems to require iptables modules to be loaded explicitly on the host (according to this ). This in someway breaks portablity.

My way (using socat: forwarding *:8889 to 127.0.0.1:8888.)

Dockerfile

...
yum install -y socat
RUN echo -e '#!/bin/bash\n./localservice &\nsocat TCP4-LISTEN:8889,fork 
TCP4:127.0.0.1:8888\n' >> service.sh
RUN chmod u+x service.sh
ENTRYPOINT ["./service.sh"]

docker-compose.yml

version: '3'
services:  
  laax-pdfjs:
    ports:
        # Switch back to 8888 on host
      - "8888:8889"
    build: .
like image 36
Marinos An Avatar answered Oct 06 '22 12:10

Marinos An