A Program inside a Docker Container Access Private Service on Host without Making It Public

Sometimes, we want to make a program running inside a docker container to access a private service which listens to 127.0.0.1 only on the host. Most of the time, we simplicity make the service listen to 0.0.0.0. But this might make the service insecure. Here, I present another way to achieve the task.

Socat

Socat is a command line based utility that establishes two bidirectional byte streams and
transfers data between them.

From its man page, it is very powerful. It supports a lot of data streams. Here, I will use it to turn the tcp socket to a unix socket on the host. Inside the docker container, I will use it to turn the unix socket back to a tcp socket.

  • Host: socat UNIX-LISTEN:/tmp/tcp2unix.sock,fork TCP:localhost:8080
  • Container: socat TCP-LISTEN:8080,reuseaddr,fork UNIX-CONNECT:/tmp/tcp2unix.sock

With the socket file mounted into a container, running the command makes the private service accessible inside the container. But it is too invasive. Later, I came up with a method. How about making the socat program a service inside docker network too?

A Socat Container

There is a socat image already: docker.io/alpine/socat. I can create a container with this command: podman container run -d --name socat -v /tmp/tcp2unix.sock:/tmp/tcp2unix.sock alpine/socat TCP-LISTEN:8080,reuseaddr,fork UNIX-CONNECT:/tmp/tcp2unix.sock.

But I can't access the socat container in another container. After running podman network inspect podman, I found both containers are not in the network. Unlike docker, podman won't add the container to the default network by default.

Adding --network podman to the command line, I could access the socat service after that. But I couldn'tt access it with the container name. After inspecting the podman network, I found dns is disabled.

So, I created a new network.

podman network create podman-proxy

podman container run -d --name socat --network podman-proxy -v /tmp/tcp2unix.sock:/tmp/tcp2unix.sock alpine/socat TCP-LISTEN:8080,reuseaddr,fork UNIX-CONNECT:/tmp/tcp2unix.sock

A Socat Systemd Service

To simplify running the command on the host, I create a systemd service.

# /etc/systemd/system/tcp2unix@.service
[Unit]
Description=Relay tcp socket with a unix socket
After=network.target

[Service]
ExecStart=socat UNIX-LISTEN:/run/tcp2unix/tcp2unix-%i.sock,fork TCP:localhost:%i
ExecReload=/usr/bin/kill -USR1 $MAINPID
PIDFile=/run/tcp2unix/tcp2unix-%i.pid
PrivateDevices=yes
RuntimeDirectory=tcp2unix
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

Unlike the ad-hoc command, after starting the service, the private service can't be accessed. The permission of the socket file is "6755" and it's owned by root. So the file can't be accessed inside the container. In the man page, I found I can add umask to change the permission.

# /etc/systemd/system/tcp2unix@.service
[Unit]
Description=Relay tcp socket with a unix socket
After=network.target

[Service]
ExecStart=socat UNIX-LISTEN:/run/tcp2unix/tcp2unix-%i.sock,fork,umask=0000 TCP:localhost:%i
ExecReload=/usr/bin/kill -USR1 $MAINPID
PIDFile=/run/tcp2unix/tcp2unix-%i.pid
PrivateDevices=yes
RuntimeDirectory=tcp2unix
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

After that, container inside the podman-proxy network can finally access the private service.