Marinos An
Marinos An

Reputation: 10880

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.

Upvotes: 21

Views: 17956

Answers (3)

BMitch
BMitch

Reputation: 265045

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.

Upvotes: 18

Marinos An
Marinos An

Reputation: 10880

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: .

Upvotes: 3

Sohail05
Sohail05

Reputation: 11

Check your docker compose version and configure it based on the version.

Compose files that do not declare a version are considered “version 1”. In those files, all the services are declared at the root of the document.

Reference

Here is how I set up my ports:

version: "3"

services:
  myservice:
    image: myimage:latest
    ports:
      - "80:80"

We can help you further if you can share the remaining of your docker-compose.yaml.

Upvotes: 0

Related Questions