43

I commonly see solutions that expose a docker container's port to the host.

In my case I want to forward a local port from one container, to another.

Let's say I run a service on container A that has a hard-coded configuration to access db on localhost 3306. But I want to run the db server on container B.

What is the best way to port-forward from A-localhost:3306 to B-IP:3306?

Marinos An
  • 9,481
  • 6
  • 63
  • 96

6 Answers6

40

Install socat in your container and at startup run

socat TCP-LISTEN:3306,fork TCP:B-IP:3306 &

This will listen locally on your 3306 and pass any traffic bidirectionally to B-IP:3306. socat is available in package named socat. So you will run any of the below commands to install it

$ yum install -y socat
$ apt install -y socat
$ apk add socat

Edit-1

You can even do this by not touching your original container

Dockerfile

FROM alpine
RUN apk update && apk add socat

Build the file as below

docker build -t socat .

Now run a container from same

docker run --name mysql-bridge-a-to-b --net=container:<containerAid> socat socat TCP-LISTEN:3306,fork TCP:BIP:3306

This will run this container on A's network. So when it listens on A's network the localhost:3306 will become available in A even though A container was not touched.

Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • 3
    There is an Alpine socat image on Docker Hub: https://hub.docker.com/r/alpine/socat/ – Joe Heffer Jun 25 '20 at 15:05
  • Even though my issue was not related directly to this, this helped me solve it.! Thanks and cheers :) – Ajay Tom George May 24 '21 at 16:17
  • `--net=container:someContainer`. Is this documented somewhere? – Marinos An Sep 22 '21 at 11:34
  • This works well for a single container, however, how should I do this for multi containers, say, mapping `A:3306 -> B:3306` and `C:3306 -> B:3306`, should I start 2 `socat` containers? – Kelvin Hu Jan 13 '22 at 08:04
  • You can use nginx with a stream forwarding as well, or multiple socats if needed – Tarun Lalwani Jan 16 '22 at 11:25
  • Using alpine/socat: `docker run --network=container:nameOrIdOfcontainerA alpine/socat TCP-LISTEN:3306,fork TCP:nameOrIpOfContainerB:3306` And documentation for [--network](https://docs.docker.com/engine/reference/run/#network-container) – dlauzon Feb 03 '22 at 21:05
  • Thank you! This works in docker compose too (sorry for the bad formatting, SO doesn't seem to support multiline comments) `socat: image: alpine/socat network_mode: service:container-a depends_on: - container-a command: "TCP-LISTEN:9090,fork,reuseaddr TCP:container-b:9090"` – Manav Oct 08 '22 at 12:10
24

You can simply run the container with network mode equal to host.

docker run --network=host ...

In that case, from the container point of view, localhost or 127.0.0.1 will refer to the host machine. Thus if your db is running in another container B that listens on 3306, an address of localhost:3306 in container A will hit the database in container B.

yamenk
  • 46,736
  • 10
  • 93
  • 87
  • this actually solved me. dockerA runs in its own network (and maybe exposed to host). dockerB is the same. my intuition was that when exposing it on `docker run -p` that will transparently make it has the host network – adonese Apr 09 '20 at 18:34
  • 4
    this is only supported on Linux: https://docs.docker.com/network/host/ – TmTron Sep 08 '20 at 07:42
  • Why not use a bridge/overlay network? – OneCricketeer Aug 03 '23 at 18:52
23

If you want container B's port to be exposed as a localhost port on container A you can start container B with the network option set to container mode to start container B on container A's network namespace.

Example:

docker run --net=container:A postgres

Where:

  • A is the name or identifier of the container you want to map into.

This will startup postgres in a container on the same network namespace as A, so any port opened in the postgres container will be being opened on the same interface as A and it should be available on localhost inside container A.

Leigh McCulloch
  • 1,886
  • 24
  • 23
0

Containers can access each other on a shared network. You dont need to expose or forward anything.

https://docs.docker.com/network/network-tutorial-standalone/

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
ZobairQ
  • 202
  • 2
  • 4
  • Only true if they share the same file. Otherwise they act as two separate containers with no access to each other and you need a portmapping. – David S May 22 '22 at 15:04
  • @DavidS You seem to imply using Compose, which is not a requirement. You can do `docker run --network` and two images can use each other's names – OneCricketeer Aug 03 '23 at 18:50
-1

This solution is way overkill for the use case stated but in the case you run across this while researching a larger scope.

Hashicorp Consul installs a localhost agent that will proxy the localhost port to the ipaddress and port of a service registered within the directory. https://www.consul.io/intro

I have no relation to Hashicorp besides using their products in systems I've built.

Stevko
  • 4,345
  • 6
  • 39
  • 66
-2

You can do it with Docker-Compose.

docker-compose.yml example:

version: '3'
services:
  web:
    build: .web
    environment:
      DB_HOST: mysql:3306  # the service name below
    ports:
     - "5000:5000"
  mysql:
    build: .mysql
    ports:
     - "3306:3306"  # optional: used for accessing from the host, not from the other container

Here you have 2 docker instances: web and mysql, so each docker instance can see each other using the names you defined as services.

Hope this helps

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
Jorge Chavez
  • 125
  • 6
  • 1
    I don't know why this was downvoted without a comment. This works best for me since I don't have to mess with figuring out the port or hostname of the other container. The URI for mysql would be e.g. "mysql:3306" – Stevko Jun 15 '20 at 07:09
  • 3
    @Stevko because the db access is hardcoded to `localhost:3306`. It is not possible to change it to link to `mysql:3306`. – Julian Pieles Sep 08 '20 at 08:03