Running multiple projects locally means port collisions — two Postgres containers
can't both bind to localhost:5432. Docker solves this with random host ports,
but now you're juggling localhost:49312 and checking docker ps every time.
Every problem has a loophole: this one watches for running containers and gives
each a dedicated loopback IP (127.0.0.x) with DNS under *.loop.test. Connect
to postgres.myapp.loop.test:5432 — the original port, every time, no
collisions.
go install github.com/matgreaves/loophole@latest
Install the macOS DNS resolver (one-time, requires sudo):
sudo loophole resolve-install
This creates /etc/resolver/loop.test pointing at loophole's built-in DNS
server.
Start the daemon (requires sudo for loopback aliases):
sudo loophole serve
That's it. Start any Docker container with published ports and loophole picks it up automatically:
$ docker run -d --name pg -p 5432 postgres
$ loophole ls
DNS NAME IP PORTS CONTAINER
pg.loop.test 127.0.0.2 5432→49312 pg
Connect using the DNS name on the original port:
psql -h pg.loop.test -p 5432
Compose containers get names based on their service and project:
$ docker compose -p myapp up -d
$ loophole ls
DNS NAME IP PORTS CONTAINER
postgres.myapp.loop.test 127.0.0.2 5432→49312 myapp-postgres-1
redis.myapp.loop.test 127.0.0.3 6379→49313 myapp-redis-1
Loophole detects Kafka wire protocol on any port and rewrites metadata responses so broker advertised addresses point back through the proxy. No configuration needed — Kafka clients see loophole's IP instead of the container's internal address:
$ docker run -d --name kafka -p 9092 apache/kafka
$ kcat -b kafka.loop.test:9092 -L
Metadata for all topics (from broker 1: 127.0.0.2:9092/1):
1 brokers:
broker 1 at 127.0.0.2:9092 (controller)
loophole serve Start the daemon
loophole ls List proxied containers
loophole resolve-install Install macOS DNS resolver (requires sudo)
loophole resolve-uninstall Remove macOS DNS resolver (requires sudo)
- Watches Docker for container start/stop events
- Allocates a loopback IP (127.0.0.2, .3, .4, ...) and creates a macOS
loopback alias (
ifconfig lo0 alias) - Starts TCP proxies on the allocated IP, listening on the container's original ports and forwarding to Docker's mapped host ports
- Registers the DNS name in a built-in DNS server on 127.0.0.1:15353
- On each new TCP connection, sniffs the first 8 bytes to detect Kafka wire protocol — Kafka connections get metadata response rewriting, everything else gets plain bidirectional forwarding
Loophole looks for the Docker socket in this order:
DOCKER_HOSTenvironment variable/var/run/docker.sock~/.docker/run/docker.sock~/.colima/default/docker.sock~/.orbstack/run/docker.sock
- macOS (uses
ifconfig lo0 aliasand/etc/resolver/) - Docker
- Go 1.25+