Self-hosted service
qoomon/docker-host avatar
qoomon/docker-host

docker-host is a sidecar container that forwards traffic to the Docker host

A docker sidecar container to forward all traffic to local docker host or any other host

1,165 stars87 forksShellMIT

At a glance

What is it?
qoomon/docker-host is a small container that gives other containers a route back to the Docker host, forwarding TCP and UDP on every port by default, and it works with rootless podman as well. The mechanism is a NAT rule set inside the sidecar, and the price is the two Linux capabilities it needs and a host application that has to listen on more than localhost.
Who is it for?
Adopt docker-host when a containerised application has to reach a service running on the Docker host, and you would rather not use host networking or publish the service on an interface the container can reach. Do not adopt it if you can run the application with the host network mode, because the sidecar adds a NAT hop and two capabilities for no benefit.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 31 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: the host is unreachable from a container by design

docker-host is a docker sidecar container to forward all traffic to the local docker host or any other host, and the readme states the scope in the first line. It is a Docker image to forward TCP and UDP traffic to the docker host.

The problem it solves is a specific and common one. A container has its own network namespace, and the host's loopback interface is not visible inside it, so an application in a container cannot reach a database, a queue or an API that is listening on 127.0.0.1 of the host. The usual workarounds are to publish the port on the host interface, which exposes it more widely than you probably want, or to use host networking, which gives up the network isolation the container was for.

This container sits in between. It is run alongside the application, it finds the host, and it forwards traffic to it, so the application can use an address inside the container network and reach the host service.

The readme is careful to say that it also works for rootless podman, and that the term docker in the documentation should be read as both docker and podman unless something says otherwise.

The implementation is a shell script, the licence is MIT, the last push was on 2026-09-22 with the v3.5.2 release the same day, and the image tags are latest, 3 and the full 3.x.x version. The topic tags are the vocabulary of the problem, with sidecar, port-forwarding, nat, nftables, iptables, egress, firewall and polyfill among them.

Finding the host, in a defined order

The container determines the Docker host address in a fixed order, and each step is a deliberate override of the one before.

The first is the environment variable DOCKER_HOST. If it is set, that address is used, and it can be an IP address or a DNS name. The readme notes that this allows you to use the image to forward traffic to arbitrary destinations, not only the Docker host, which makes the container a general purpose port forwarder as well as a host bridge.

The second is to try to resolve the host address from DNS names, using the docker hostname host.docker.internal, and for podman the hostname host.containers.internal. These are the names the runtimes inject into the container's resolver, so this works without any configuration on a normal installation.

The third is the default gateway, found with ip -4 route show default. That is the bridge gateway address, and it is the fallback when the environment variable is unset and the DNS name does not resolve.

The order matters for a reason that is not obvious until you hit it. The DNS name is a per-runtime convention that differs between docker and podman and does not exist on every setup, while the default gateway is a property of the network namespace and is always there. So the resolution degrades from the most specific to the most universal, and the first two steps are there so that you can point the sidecar somewhere else when the default is not what you want.

Every port is forwarded by default, which is the setting to change

By default all ports, 1 to 65535, are forwarded to the Docker host.

That default is the thing to think hardest about, because it is the opposite of what a careful deployment would choose. A sidecar that opens a forwarding path for every port on the host, for every container that talks to it, is a wide surface, and the documentation gives you the two ways to narrow it.

The first is the PORTS environment variable, which takes a space and or comma separated list of ports and port ranges, for example '443, 8000-9000'.

The second is a port mapping in the same variable, with the form CONTAINER_PORT:HOST_PORT, for example 443:8443 and 8000-9000:5000-6000, so a range on one side can be mapped onto a different range on the other.

The same variable therefore carries two different meanings depending on whether it contains a colon, which is a little awkward but keeps the configuration to one variable.

The readme's framing for the whole tool is that it removes the need for an application to know anything about the network, which is a genuine convenience. The cost is that the convenience defaults to forwarding everything, so a deployment that does not set PORTS is more open than the person running it probably intends.

The Linux caveat is the one that will bite you

There is an IMPORTANT block in the readme for Linux systems, and it is the most practically important section in the file.

You have to bind your host applications to 0.0.0.0 or the bridge network gateway, in addition to 127.0.0.1. The readme gives the command that finds the bridge gateway address.

bash
docker network inspect bridge --format='{{( index .IPAM.Config 0).Gateway}}'

That is the whole reason this kind of container is not a one liner. A service listening only on the host's loopback is genuinely unreachable from another network namespace, so the sidecar has to be able to reach it on a routable address, and the service has to be listening on one. Binding to 0.0.0.0 is the usual answer, and it is a real change in the host application's exposure, which is why the readme calls it out separately from podman.

For rootless podman, the readme says it is sufficient to bind to localhost, assuming a default podman installation, because rootless podman uses a different network arrangement.

The second bullet is the one people forget. You might need to configure the firewall of the host system to allow the docker-host container to communicate with the host on your relevant port, with a link to an issue comment for the details. So the checklist is three things: bind the service to the right address, allow the traffic through the firewall, and then the sidecar.

macOS and podman need a kernel module

The macOS section is short and it is podman specific, and it is the kind of detail that is easy to lose.

Under Podman Only, you probably need to add the nf_nat kernel module to the podman machine by running these commands.

shell
podman machine ssh

sudo modprobe nf_nat

The reason is in the image itself, and it explains a choice in the Dockerfile. The container installs both iptables and iptables-legacy, and the nf_nat module is what provides the NAT support that the forwarding rules need. On a Mac, the podman machine is a Linux virtual machine, and the module has to be loaded inside it.

The word probably in the readme is doing honest work, because whether the module is already loaded depends on the podman machine image and on whether you have done this before.

The same mechanism explains the two Linux capabilities the container is always given, NET_ADMIN and NET_RAW. Writing iptables rules in a container namespace requires NET_ADMIN, and the raw socket capability is what the NAT and forwarding rules need. Any example that runs this image shows those capabilities being added, and they are not optional decoration.

Three ways to wire it up

The examples section covers three connection styles, and they differ in how the application finds the host.

The simplest is the Docker Link, where the sidecar is run with a name and the capabilities.

sh
docker run \
  --name 'docker-host' \
  --cap-add=NET_ADMIN --cap-add=NET_RAW \
  --restart on-failure \
  -d qoomon/docker-host

The application container then uses the link name as the address, for example nc 'docker-host' 2323 -v for TCP and the same with -u for UDP.

The second is a Docker Network, where the sidecar is given a network alias of docker-host, and the application container is attached to the same user defined network. The readme shows the network being created with a random name, the sidecar being run with --net and --network-alias, and the application using the alias as the hostname.

The third is Docker Compose, and the compose file in the repository is the shortest form of the whole thing.

yaml
services:
    dockerhost:
        image: qoomon/docker-host
        cap_add: [ 'NET_ADMIN', 'NET_RAW' ]
        mem_limit: 8M
        restart: on-failure
    tcp_message_emitter:
        depends_on: [ dockerhost ]
        image: alpine
        command: [ "sh", "-c", "while :; do date; sleep 1; done | nc 'dockerhost' 2323 -v"]

The compose form is where the design shows. The sidecar is given an eight megabyte memory limit and a restart policy, which tells you how little it does and how much you care about it staying up, and the emitter is an ordinary Alpine container that pipes a timestamp into netcat.

The whole container is an Alpine image and a script

The repository is four files and a Dockerfile, and the image is built from one.

dockerfile
FROM alpine:3.24.2

RUN apk --no-cache upgrade \
  && apk --no-cache add  \
    iptables iptables-legacy \
    libcap

COPY ./entrypoint.sh /

ENTRYPOINT ["/entrypoint.sh"]

The base image is Alpine 3.24.2, and the packages are the two implementations of iptables plus libcap for the capabilities. Installing iptables-legacy alongside the modern one is there for compatibility with the rules the script writes on different hosts.

The entrypoint is a single shell script copied to the root of the filesystem and set as the entrypoint, and the whole of the container's behaviour is in it. There is no daemon, no agent and no long lived process beyond the rules it sets up.

The top level listing matches: a .github/ directory, the Dockerfile, a LICENSE file, the README, a docker-compose.yml and the entrypoint.sh. The compose file at the root is the one in the repository, with the service named dockerhost rather than docker-host, and it is the same eight megabyte limit and restart policy as the example in the readme.

The three example emitters in it, two of which pipe a timestamp into netcat, exist to prove the forwarding works in both directions and for both protocols, which is the fastest way to check a NAT rule set that someone else wrote.

Editorial conclusion

Adopt docker-host when a containerised application has to reach a service running on the Docker host, and you would rather not use host networking or publish the service on an interface the container can reach. Do not adopt it if you can run the application with the host network mode, because the sidecar adds a NAT hop and two capabilities for no benefit. Verify first that the host service binds to 0.0.0.0 or to the bridge gateway rather than to 127.0.0.1, that the host firewall permits the traffic from the container, and that the PORTS environment variable restricts the forwarding to what you actually need, since the default forwards the entire port range.

Frequently asked questions

What does the qoomon/docker-host container do?

It is a docker sidecar container that forwards TCP and UDP traffic to the local docker host or any other host, so an application container can reach a service running on the host. It also works for rootless podman.

How does the container find the Docker host?

In order: the DOCKER_HOST environment variable if it is set, which may be an IP address or a DNS name and may point anywhere; then the host.docker.internal docker name or the host.containers.internal podman name resolved through DNS; and finally the default gateway found with ip -4 route show default.

Which ports does docker-host forward?

By default all of them, 1 to 65535. The PORTS environment variable restricts that to a list of ports and ranges such as '443, 8000-9000', and it also accepts a CONTAINER_PORT:HOST_PORT mapping such as 443:8443.

What do I have to change on the host for this to work on Linux?

Your host application has to bind to 0.0.0.0 or to the bridge network gateway rather than only to 127.0.0.1, and you may need to configure the host firewall to allow the container to reach the host on the relevant port. The readme gives the command that prints the bridge gateway address.

Why does the container need NET_ADMIN and NET_RAW?

Because it writes iptables rules inside its own network namespace to perform the forwarding. NET_ADMIN allows the rules to be written and NET_RAW provides the raw socket access the NAT and forwarding rules need, which is why every example adds both capabilities.

What is inside the docker-host image?

An Alpine 3.24.2 base with iptables, iptables-legacy and libcap installed, plus a single entrypoint.sh script that does all of the work. The compose examples give it an eight megabyte memory limit and a restart policy.

Official sources

  1. Issues
  2. License: MIT
  3. qoomon/docker-host on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/qoomon-docker-host.svg)](https://hysenlabs.com/projects/qoomon-docker-host)

Was this useful?