Accessing a host service from within a Docker container is a common requirement. For instance, you might have a HTTP proxy running on the host system at 127.0.0.1:8080
that you want to access from a Docker container.
One straightforward solution is to use --network host
for the Docker container or network_mode: host
in Docker Compose. However, this approach can lead to complications. If you use network_mode: host
for one service, you may find yourself needing to use it for other services or publish their ports just to enable them to communicate with each other. This can quickly result in losing the benefits of Docker Compose's default network.
A more elegant solution is to connect to the special DNS name host.docker.internal
, which resolves to the internal IP address used by the host (for example, using host.docker.internal:8080
to connect to the proxy). On Linux, you need to pass --add-host=host.docker.internal:host-gateway
to the docker
command. The Docker Compose equivalent is:
extra_hosts:
- host.docker.internal:host-gateway
However, this method won't work, since our host service is listening on 127.0.0.1
. One workaround is to bind the host service to 0.0.0.0
, but this exposes it to the outside world, which is often undesirable. The other workaround is to bind the host service to 172.17.0.1
, but other apps that rely on 127.0.0.1:8080
will break. Or we can bind to both 127.0.0.1
and 172.17.0.1
, but the host service may have no configuration option to specify the address or bind to multiple addresses.
Solution using Socat
I found a simple and effective solution to this problem using the socat
command. Socat is a command-line utility that establishes two bidirectional byte streams and transfers data between them. In this context, it can be used to listen on a TCP port on host.docker.internal
and forward to the host service. This allows you to connect to the host service using host.docker.internal
.
Here's how to set this up in Docker Compose for our HTTP proxy example:
services:
proxy-relay:
image: alpine/socat:latest
network_mode: host
command: TCP-LISTEN:8080,fork,bind=host.docker.internal TCP-CONNECT:127.0.0.1:8080
extra_hosts:
- host.docker.internal:host-gateway
my-service:
image: some-image
command: my-command --connect-to host.docker.internal:8080
extra_hosts:
- host.docker.internal:host-gateway
In this configuration, the proxy-relay
service listens on host.docker.internal:8080
and forwards to 127.0.0.1:8080
. This allows my-service
to access the proxy without using network_mode: host
and without changing the bind address of the proxy. We also did not hard-code 172.17.0.1
anywhere.
Did you find this post useful? Please leave your feedback in the comments. 😊️
Top comments (11)
Thank you for the great article.
You're welcome. :)
Awesome! I used it to port my proxy, which was alive in my host machine, to my containers without giving them access to the host network. Thanks Mohammad Javad :)
You're welcome! I'm glad that this post was helpful to you.
Mate, cheers for this. I've been looking for a solution like this for days.
Thank you for your feedback!
You can bind the database server to both 127.0.0.1 and 172.17.0.1. SMH.
Also. If you're connecting on the same host it's better to use UNIX sockets. Just mount the socket dir into the container.
Thanks for your feedback.
I mentioned this solution in the article: "The other workaround is to bind the database server to 172.17.0.1, but other apps that rely on 127.0.0.1:5432 will break."
I know that PostgreSQL can bind to multiple IP addresses (so we can bind to both
127.0.0.1
and172.17.0.1
), but I did not mention that our database is PostgreSQL. Plus, "database" is just an example. It could be any service, for example, an HTTP proxy, or anything else that other apps expect to be available at127.0.0.1
and that service may have no configuration to bind to multiple IP addresses or use UNIX sockets.And what if the IP
172.17.0.1
changes in a new version of Docker? This solution does not rely on hard-coded IP172.17.0.1
.Update: I edited the post and made it more general (removed "database" and 5432).
Saved many hours. It works as it claims. Thanks!!!
hanks a lot, you saved me with this!
Let me thank you for that great article.
Could you please point out how to use socat, connecting to port 53 UDP on a host running a podman container listening to 5353 UDP?