Note: Tested with Podman Engine 4.9.1 & v5.0.3.
Note: This project is a fork of chipmk/docker-mac-net-connect for Docker, an excellent tool that inspired this version for Podman.
Connect directly to Podman containers on macOS via IP address.
- L3 connectivity: Connect to podman containers from macOS host (without port binding).
- Lightweight: Based on WireGuard (built-in to Linux kernel).
- Hands-off: Install once and forget. No need to re-configure every time you restart your Mac or podman machines.
- Automatic: Docker networks are automatically added/removed from macOS routing table.
- No bloat: Everything is handled by a single binary. No external dependencies/tools are needed.
You must be using podman for macOS v5.0.0 or higher to use this tool.
The wireguard
kernel module is not enabled in podman machines out of the box. Running this service will see it check for the existence of this module and, if necessary, enable it via via a podman system connection as root. This likely only works on --rootful
podman machines for the time being.
# Install via Homebrew
brew install jasonmadigan/tap/podman-mac-net-connect
# Run the service and register it to launch at boot
# Note: running with sudo is important
sudo brew services start jasonmadigan/tap/podman-mac-net-connect
This Homebrew formulae is built using go
. When Homebrew installs a formulae, it strips away local environment variables and configuration, including configuration set using go env
.
Some users require changing GOPROXY
due to firewalls. This formulae adds special support for GOPROXY
using HOMEBREW_GOPROXY
:
HOMEBREW_GOPROXY=https://my-proxy-url brew install jasonmadigan/tap/podman-mac-net-connect
After installing, you will be able to do this:
# Run an nginx container
podman run --rm --name nginx -d nginx
# Get the internal IP for the container
podman inspect nginx --format '{{.NetworkSettings.IPAddress}}'
# internal IP for the container
10.88.0.19
# Make an HTTP request directly to its IP
curl -I 10.88.0.19
# Response
HTTP/1.1 200 OK
Server: nginx/1.25.4
Date: Wed, 27 Mar 2024 08:59:22 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Wed, 14 Feb 2024 16:03:00 GMT
Connection: keep-alive
ETag: "65cce434-267"
Accept-Ranges: bytes
To run locally, build the client image:
make build-podman
Run (as root):
sudo make run-go
Accessing containers directly by IP (instead of port binding) can be useful and convenient.
Podman on macOS does not expose container networks directly on the macOS host. Podman on macOS works by running a Linux VM under the hood (using qemu
) and creates containers within that VM.
Podman on macOS supports connecting to containers over Layer 4 (port binding), but not Layer 3 (by IP address).
Create a minimal network tunnel between macOS and the Linux VM running Podman's containers. The tunnel is implemented using WireGuard.
WireGuard is an extremely lightweight and fast VPN. It’s also built in to the Linux kernel, which means no background processes/containers are required. It is the perfect tool for this application.
A lightweight customized WireGuard server (podman-mac-net-connect
) runs on your macOS host and creates a virtual network interface (utun
) that acts as the link between your Mac and the Docker Desktop Linux VM.
Since WireGuard is built into the Linux kernel, all we need to do is configure the VM with a virtual network interface that links to the macOS host. No background processes or containers are required.
How do we configure the VM? A one-time container is deployed with just enough privileges to configure the Linux host’s network interfaces (—-cap-add=NET_ADMIN
+ -—net=host
).
The container creates the interface, configures WireGuard, then exits and is destroyed. The WireGuard interface continues working after the container is gone because it was created on the Linux host’s network namespace, not the container’s.
The server on macOS monitors your podman container networks and automatically adds their subnets to your macOS routing table (routing through the utun
interface). Now you can connect to any container directly by it’s IP address from your macOS host. Eg.
# Run an nginx container
$ podman run --rm --name nginx -d nginx
# Get the internal IP for the container
$ podman inspect nginx --format '{{.NetworkSettings.IPAddress}}'
172.17.0.2
# Make an HTTP request directly to its IP
$ curl -I 172.17.0.2
HTTP/1.1 200 OK
Server: nginx/1.21.3
Date: Thu, 11 Nov 2021 21:00:37 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 07 Sep 2021 15:21:03 GMT
Connection: keep-alive
ETag: "6137835f-267"
Accept-Ranges: bytes
Other great solutions have been created to solve this, but none of them are as turn-key and lightweight as we wanted.
-
- Requires installing third party
tuntap
kernel extension - Requires manually re-running a script every time the Docker VM restarts to bring the network interface back up
- Docker network subnets have to be routed manually
- Requires installing third party
-
- Requires installing an OpenVPN client (ie.
Tunnelblick
) - Requires an OpenVPN server container to be running at all times in order to function
- Docker network subnets have to be routed manually
- Requires installing an OpenVPN client (ie.
This tool piggybacks off of WireGuard which has gone through numerous audits and security tests (it is built-in to the Linux kernel after all). The docker-mac-net-connect
server generates new private/public key pairs for each WireGuard peer every time it runs. No values are hard-coded.
Network traffic runs directly between the macOS host and local Linux VM - no external connections are made.
This tool was designed to assist with development on macOS, only.
The server detects when the podman stops and automatically reconfigures the tunnel when it starts back up.
Yes, the server watches the Podman daemon for both network creations and deletions and will add/remove routes accordingly.
For example, let's create a Podman network with subnet 172.200.0.0/16
:
# First validate that no route exists for the subnet
sudo netstat -rnf inet | grep 172.200
# Create the docker network
podman network create --subnet 172.200.0.0/16 my-network
# Check the routing table - a new route exists
sudo netstat -rnf inet | grep 172.200
172.200 utun0 USc utun0
# Remove the docker network
podman network rm my-network
# The route has been removed
sudo netstat -rnf inet | grep 172.200
No, routes are tied to the utun
device created by the server. If the server dies, the utun
interface will disappear along with its routes.
Root permissions are required by the service to:
- Create a
utun
network interface - Configure the
utun
interface (ifconfig
) - Add and remove routes in the routing table (
route
)
This app tries to minimize opportunity for privilege escalation by following the principle of least privilege (PoLP). With that said, macOS has no concept of fine-grained admin privileges (ie. capabilities), so running as sudo
is required.
MIT