DEV Community

Cover image for Accessing Host Services from Docker Containers
Mohammad Javad Naderi
Mohammad Javad Naderi

Posted on • Edited on

Accessing Host Services from Docker Containers

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


Enter fullscreen mode Exit fullscreen mode

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.

Diagram of Accessing Host Service from Docker Container

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


Enter fullscreen mode Exit fullscreen mode

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 (10)

Collapse
 
suriya786 profile image
suriya786

Thank you for the great article.

Collapse
 
mjnaderi profile image
Mohammad Javad Naderi

You're welcome. :)

Collapse
 
itismoej profile image
Mohammad

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

Collapse
 
mjnaderi profile image
Mohammad Javad Naderi

You're welcome! I'm glad that this post was helpful to you.

Collapse
 
jquintanilla4 profile image
jquintanilla4

Mate, cheers for this. I've been looking for a solution like this for days.

Collapse
 
mjnaderi profile image
Mohammad Javad Naderi

Thank you for your feedback!

Collapse
 
ledlamp profile image
Lamp

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.

volumes:
  - "/run/postgresql:/run/postgresql"
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mjnaderi profile image
Mohammad Javad Naderi • Edited

Thanks for your feedback.

You can bind the database server to both 127.0.0.1 and 172.17.0.1.

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 and 172.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 at 127.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 IP 172.17.0.1.

Update: I edited the post and made it more general (removed "database" and 5432).

Collapse
 
enilec_vepres profile image
Enilec

hanks a lot, you saved me with this!

Collapse
 
computerhirn profile image
Ralf • Edited

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?