This is my lightweight local development environment using dnsmasq, Docker, and Traefik running on macOS Monterey.
- Support for local development of multiple docker services with API interdependencies
- Ability to use *.test domain names from Mac host
- Ability to use same domain names inside Docker containers
- Support for HTTP and TCP routes
- Support for HTTPS (without self-signed certificate so far)
- No more messing around in /etc/hosts
This is my current set up:
- macOS Monterey (12.7)
- Homebrew (4.2)
- dnsmasq (2.90)
- Docker Desktop for Mac (4.28)
The instructions should also work with older versions.
- Create persistent loopback interface for IP 10.254.254.254
- Install dnsmasq using IP 10.254.254.254 for nameserver and address target
- Launch Traefik and other containers using Docker Compose
Create a "launchd" daemon that configures an additional IPv4 address.
cat << EOF | sudo tee -a /Library/LaunchDaemons/ch.tebe.loopback1.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ch.tebe.loopback1</string>
<key>ProgramArguments</key>
<array>
<string>/sbin/ifconfig</string>
<string>lo0</string>
<string>alias</string>
<string>10.254.254.254</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
Launch service:
sudo launchctl load /Library/LaunchDaemons/ch.tebe.loopback1.plist
Make sure it works:
LaunchDaemons % sudo launchctl list | grep ch.tebe
- 0 ch.tebe.loopback1
Restart Mac and check ifconfig:
ifconfig lo0
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
inet 127.0.0.1 netmask 0xff000000
inet6 ::1 prefixlen 128
inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
inet 10.254.254.254 netmask 0xff000000
nd6 options=201<PERFORMNUD,DAD>
Install dnsmasq using Homebrew:
brew update # Always update Homebrew and the formulae first
brew install dnsmasq
Start dnsmasq service:
sudo brew services start dnsmasq
Open /usr/local/etc/dnsmasq.conf
and add/uncomment the following line:
conf-dir=/usr/local/etc/dnsmasq.d,*.conf
Create custom conf file:
mkdir -p /usr/local/etc/dnsmasq.d
touch /usr/local/etc/dnsmasq.d/development.conf
Add routing rule for *.test domain names:
address=/.test/10.254.254.254
Add custom resolver:
sudo mkdir /etc/resolver
Create a file /etc/resolver/test
for the *.test domain names and add this line:
nameserver 10.254.254.254
Check that the resolver is registered.
scutil --dns
...
resolver #8
domain : test
nameserver[0] : 10.254.254.254
flags : Request A records
reach : 0x00030002 (Reachable,Local Address,Directly Reachable Address)
...
Check the dnsmasq setup:
ping -c 1 google.com # Make sure you can still access the outside world!
ping -c 1 mysite.test
ping -c 1 my.other.site.test
Install Docker Desktop:
https://www.docker.com/products/docker-desktop
Clone project from Github:
git clone https://github.com/tbreuss/local-dev.git
Start services using Docker Compose:
cd local-dev
docker-compose up
Check that everything works as expected.
Open http://whoami.test
with your favorite browser.
You should see something like:
Hostname: eb7f1da188d7
IP: 127.0.0.1
IP: 172.18.0.5
RemoteAddr: 172.18.0.2:45232
GET / HTTP/1.1
Host: whoami.test
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Dnt: 1
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 172.18.0.1
X-Forwarded-Host: whoami.test
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 73db93d4c8e8
X-Real-Ip: 172.18.0.1
Now, open https://whoami.test
with your favorite browser.
The browser displays a NET::ERR_CERT_AUTHORITY_INVALID warning or similar, but lets you proceed to the website if you choose to.
You should see a similar output like above.
Make a cURL call from one docker container to another:
docker-compose exec adminer curl http://whoami.test
Hostname: eb7f1da188d7
IP: 127.0.0.1
IP: 172.18.0.5
RemoteAddr: 172.18.0.2:45238
GET / HTTP/1.1
Host: whoami.test
User-Agent: curl/7.80.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.18.0.1
X-Forwarded-Host: whoami.test
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 73db93d4c8e8
X-Real-Ip: 172.18.0.1
Try the same using https:
docker-compose exec adminer curl --insecure https://whoami.test
You should see a similar output like above.
Don't forget to check the same after rebooting your Mac.
At the time of writing this repo includes configs for the following Docker images:
Thanks to the authors of these helpful blog posts: