Self-hosted service
chipmk/docker-mac-net-connect avatar
chipmk/docker-mac-net-connect

docker-mac-net-connect gives macOS a route to Docker container IPs

Connect directly to Docker-for-Mac containers via IP address 🐳 💻

666 stars65 forksGoMIT

At a glance

What is it?
docker-mac-net-connect is a Go binary that puts a WireGuard tunnel between the macOS host and the Docker Desktop Linux VM, so containers are reachable by IP address instead of through published ports, with no agent left running in the VM.
Who is it for?
docker-mac-net-connect fits a developer on macOS whose workflow depends on reaching a container by its address, whether for a debugger attaching to a port nobody published, a test suite that assumes a fixed subnet, or a lab that needs the internal bridge range. It is the wrong tool on Linux, where containers are already on the host network stack, and the wrong tool for a team that would rather publish ports and keep the host routing table untouched.
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 96 days ago.
What is it written in?
Mainly Go, 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

Why a container IP does not work on macOS by default

On Linux, a container is reachable at its address on the same network stack as the host, so a service listening on port 80 inside a container can be curled at 172.17.0.2 without anything being published.

Docker for Mac breaks that. The README explains why: unlike Docker on Linux, Docker-for-Mac does not expose container networks directly on the macOS host, because it runs a Linux VM under the hood using hyperkit and creates the containers inside that VM. Docker-for-Mac supports connecting to containers at Layer 4, which is the port binding model, but not at Layer 3, which is addressing a container by IP.

The fix is to build the missing Layer 3 path. docker-mac-net-connect creates a minimal network tunnel between macOS and the Docker Desktop Linux VM, and the tunnel is implemented with WireGuard.

The choice of WireGuard is explained in the README rather than assumed. It is described as an extremely lightweight and fast VPN that is built into the Linux kernel, which means no background processes or containers are required on the VM side, and the project presents that as the reason it fits this particular job.

For the user, the payoff is that the familiar address works. The README frames the benefit as convenience rather than necessity, which is the right framing: publishing a port works fine, but it costs a host port, it collides with whatever already listens there, and it is a per-container step that has to be repeated every time the container is recreated.

A one-shot privileged container, then nothing left running

The mechanism is worth understanding, because the design is what makes the tool hands-off.

On the macOS side a lightweight customised WireGuard server runs on the host and creates a virtual network interface, the utun device, which acts as the link between the Mac and the Docker Desktop Linux VM. On the VM side, because WireGuard is in the Linux kernel, all that is needed is a virtual network interface configured to link back to the macOS host, with no background processes or containers involved.

The configuration of the VM is done by a one-time container that is given just enough privilege to configure the Linux host's network interfaces, using the NET_ADMIN capability and host networking. That container creates the interface, configures WireGuard, and then exits and is destroyed. The interface keeps working afterwards because it was created in the Linux host's network namespace rather than in the container's, which is the whole reason a one-shot container is enough.

The macOS server then monitors the Docker container networks and automatically adds their subnets to the macOS routing table, routed through the utun interface. That is what makes the tool automatic rather than manual: there is no list of subnets to maintain, and no configuration to redo after the Mac or the Docker daemon restarts. The README's description of the service as hands-off, installed once and forgotten, comes directly from this design.

The repository layout matches. The client directory holds the one-time setup container, the networkmanager directory handles the routing table work, and the version directory is injected at build time.

Installing through Homebrew and running it at boot

The install path is Homebrew, and the service is registered to start at boot so the tunnel exists before the first container runs:

bash
# Install via Homebrew
$ brew install chipmk/tap/docker-mac-net-connect

# Run the service and register it to launch at boot
$ sudo brew services start chipmk/tap/docker-mac-net-connect

The tap is chipmk/tap, so the package comes from the maintainer's own Homebrew repository rather than from Homebrew core. Registering it with brew services is what makes the connection survive a reboot of the Mac and a restart of the Docker daemon.

There is one wrinkle for users behind a firewall. The formula is built with Go, and Homebrew strips local environment variables and configuration when it installs a formula, including anything set through go env. Users who need to change GOPROXY therefore have a dedicated variable to set:

bash
HOMEBREW_GOPROXY=https://my-proxy-url brew install chipmk/tap/docker-mac-net-connect

The requirements are stated up front. Docker Desktop 3.6.0 or higher is one option, and Colima is the other, with the note that other Lima based runtimes are likely to work but have not been tested. The distinction matters if you use Colima, since the project is explicit that it has not verified those runtimes itself.

The README also names the alternatives it replaces. Publishing a port with the -p flag is the default Docker workflow and needs no tunnel at all, and a plain host network mode on Linux gives the same direct addressing without a VPN. The tool earns its place when you want addressable containers on macOS without giving up host ports or giving each container a fixed published port.

First use: read the container IP and curl it

The verification loop is short, and it is worth running it as written before trusting the tunnel with anything real.

Start a container:

bash
# Run an nginx container
$ docker run --rm --name nginx -d nginx

Read its address on the Docker network:

bash
# Get the internal IP for the container
$ docker inspect nginx --format '{{.NetworkSettings.IPAddress}}'
172.17.0.2

Then make a request straight to that address, with no port mapping in sight:

bash
# Make an HTTP request directly to its IP
$ curl -I 172.17.0.2
HTTP/1.1 200 OK
Server: nginx/1.21.3

The response is the container's own HTTP server answering on port 80, which proves the Layer 3 path is working end to end rather than that some port mapping leaked through. The same three commands are repeated later in the README as the canonical example, which is a decent sign that they are the intended first use.

The practical difference from the ordinary Docker workflow is now visible. There is no -p flag, so no host port is consumed, and the container can be reached on the address it already had. That matters in several everyday situations: a client hard-coded to a fixed address, a second container that needs to reach the first by IP, a debugging session that attaches to a port you did not think to publish, and integration tests that assume a predictable range.

Internal networks work, and the NAT that makes them safe

Docker's internal networks let containers talk to each other while blocking access to external networks, and the README says they are supported out of the box with this tool. You can reach containers on an internal network from the macOS host just as you would any other container.

bash
# Create an internal network
$ docker network create --internal --subnet 172.30.0.0/24 my-internal

# Run a container on it
$ docker run --rm -d --name nginx --network my-internal --ip 172.30.0.2 nginx

# Connect directly from macOS
$ curl -I 172.30.0.2
HTTP/1.1 200 OK

The mechanism behind that is a translation rather than a plain route. The macOS host's WireGuard IP is NAT'd to the Docker bridge gateway IP, so the container sees the traffic as arriving from inside its own network. The reason is that internal networks deliberately have no default gateway, so without NAT the container would have no way to send a reply back to the host.

The safety argument is spelled out, and it is the part to be convinced by. Only the local macOS host can reach internal containers through the tunnel. Other devices on the LAN cannot reach them unless IP forwarding has been explicitly enabled on the Mac, which is off by default. Even with forwarding enabled, LAN traffic is not NAT'd, so the container has no route to send a reply, which in practice leaves internal containers unreachable from the LAN.

That is a defence in depth argument rather than an absolute one, and it depends on the default of forwarding being off. Anyone who turns it on is changing the picture, which is exactly what the next section is about.

Reaching containers from the LAN takes two deliberate steps

By default the tool only gives the macOS host a route to the containers. Extending that to other devices on the local network is possible, and the README documents the price.

The first step is enabling IP forwarding on the Mac so it will forward packets from the LAN into the tunnel:

bash
sudo sysctl -w net.inet.ip.forwarding=1

To make that survive a reboot, the setting is added to /etc/sysctl.conf. The second step is a static route on the router, pointing the Docker network subnet at the Mac's LAN address. The README gives the shape of that entry with a network of 192.168.2.0, a netmask of 255.255.255.0 and a gateway of 192.168.1.6, and notes that not all routers support static routes, so you may need one that supports OpenWRT or something similar.

The routing works differently from the local case, and the difference matters. Traffic from LAN devices is forwarded through the tunnel without NAT, so containers see the real source IP of each device rather than a single translated address. That is useful for services that log or authorise by client address, and it is a different trust model from the NAT'd local path, where everything appears to come from the bridge gateway.

The practical objection to this configuration is that it puts your Mac in the packet path for container traffic across the whole network segment. Any routing problem between the router and the Mac now affects container reachability, and the failure mode is a silent drop rather than an error in the tool. For a lab on one desk it is fine. For a shared office network it deserves a second look.

Build pipeline, releases and the state of the project

The repository is a small Go project with a tidy layout, and the Makefile shows how the two pieces are produced.

The main binary is built with the version and setup image baked in through linker flags, and the setup container is built from the client directory into a registry image under ghcr.io:

make
build-go:
	go build -ldflags "-s -w $(LD_FLAGS)" $(PROJECT)

build-docker:
	docker build -t $(SETUP_IMAGE):$(VERSION) ./client

The same file carries the checks, with go vet and golangci-lint run against both the host program and the client compiled for Linux, and an e2e target that runs a shell script from the scripts directory. The release image is built for two architectures at once, linux/amd64 and linux/arm64, and pushed to the registry.

The module definition confirms the two hard dependencies. The Docker client is at version 28.5.2, and the WireGuard implementation comes from golang.zx2c4.com/wireguard with its wgctrl control package, the latter being what lets a program create and configure a WireGuard interface on a running kernel.

On releases, the recent tags are v0.1.7 on 2026-02-18, v0.1.6 the same day, and v0.1.5 on 2026-02-13, which is a tight cluster of patch releases for a small tool. The last push to the repository was on 2026-07-19. The licence is MIT with a LICENSE file at the top level, which is permissive enough for a company to run the binary internally, with the usual notice obligation. The repository also carries AGENTS.md and CLAUDE.md, files that record the conventions used when working on the code with AI assistance, alongside a CONTRIBUTING.md.

Editorial conclusion

docker-mac-net-connect fits a developer on macOS whose workflow depends on reaching a container by its address, whether for a debugger attaching to a port nobody published, a test suite that assumes a fixed subnet, or a lab that needs the internal bridge range. It is the wrong tool on Linux, where containers are already on the host network stack, and the wrong tool for a team that would rather publish ports and keep the host routing table untouched. Before installing it, confirm you are on Docker Desktop 3.6.0 or higher or on a Lima based runtime such as Colima, leave IP forwarding off unless you deliberately want LAN access, and read the route section of the README before enabling it, since that step makes containers reachable from the local network and depends on router support for static routes.

Frequently asked questions

What problem does docker-mac-net-connect solve?

Docker for Mac runs containers inside a Linux VM and supports port bindings at Layer 4, but not addressing containers by IP at Layer 3. This project creates a tunnel between macOS and that VM so a container can be reached directly at its Docker address.

How do I install docker-mac-net-connect on macOS?

Install the formula with brew install chipmk/tap/docker-mac-net-connect and start the service with sudo brew services start chipmk/tap/docker-mac-net-connect, which registers it to launch at boot. Docker Desktop 3.6.0 or higher is required, or a Lima based runtime such as Colima.

Does anything keep running inside the Docker VM?

No. A one-time container with the NET_ADMIN capability and host networking creates and configures the WireGuard interface, then exits and is destroyed. The interface keeps working because it was created in the Linux host's network namespace, and the macOS side maintains the routes.

Can other devices on my LAN reach the containers?

Not by default. You have to enable IP forwarding on the Mac with sysctl -w net.inet.ip.forwarding=1 and add a static route on your router pointing the Docker subnet at the Mac. LAN traffic is forwarded without NAT, so containers see the real source address of each device.

How do I set GOPROXY when installing the Homebrew formula?

Homebrew strips local environment variables when it installs a formula, so the project adds a dedicated HOMEBREW_GOPROXY variable. Set it in the install command, for example HOMEBREW_GOPROXY=https://my-proxy-url brew install chipmk/tap/docker-mac-net-connect.

Official sources

  1. chipmk/docker-mac-net-connect on GitHub
  2. Issues
  3. License: MIT
  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/chipmk-docker-mac-net-connect.svg)](https://hysenlabs.com/projects/chipmk-docker-mac-net-connect)

Was this useful?