Skip to content

Instantly share code, notes, and snippets.

@ju1ius
Last active October 28, 2023 20:17
Show Gist options
  • Save ju1ius/75e6bf0f2bd082bf32b0268be1202287 to your computer and use it in GitHub Desktop.
Save ju1ius/75e6bf0f2bd082bf32b0268be1202287 to your computer and use it in GitHub Desktop.
Using reserved TLDs for local development in Docker

Using reserved TLDs for local development in Docker

TL;DR

If you want to use a reserved TLD like test for local development, read one of the following:

Rationale

The usual way we use private TLDs for local development is by making a domain name point to the machine's loopback network interface, by assigning it an IP in the 127.0.0.1/8 range.

But inside a container, an IP in this range resolves to the loopback interface of the container, not that of the host machine.

We can solve that by manually adding network aliases to each and every container that needs to be able to resolve our domain name, but that gets repetitive pretty quickly, especially in large projects or when using an external HTTP proxy container to serve all our projects.

The solution we'll use here has two fundamental steps:

  1. Assign an additional private IP address to our machine's loopback interface.
  2. Configure DNS on our machine to make our domain name point to this address, even inside a docker container.

For step 1. we'll need to pick an IP reserved for private networking, but that is not in the following ranges:

  • 127.0.0.1/8: already reserved for the loopback interface
  • 172.16.0.0/12: usualy used by Docker for it's internal networking,
  • 192.168.0.0/16: usually used by home routers

That leaves us with the 10.0.0.0/8 range. We'll use 10.254.254.254, as it is unlikely to be used by anything else, but you can use any other available private IP.

For step 2. we'll use the dnsmasq program so that all domain names ending in .test are resolved to 10.254.254.254.

Now you can get back to the top and read the instructions for your system.

DNS setup for Linux w/ NetworkManager

Create a routable loopback IP

sudo ip addr add 10.254.254.254 dev lo

To make this change persistent across reboots:

sudo tee /etc/network/interfaces.d/loopback <<'EOF'
iface lo inet loopback
  up /usr/bin/ip addr add 10.254.254.254 dev lo
EOF

Enable NetwokManager's dnsmasq plugin:

sudo tee /etc/NetworkManager/conf.d/00-use-dnsmasq.conf <<'EOF'
[main]
dns=dnsmasq
EOF

Setup dnsmasq

We need to tell dnsmasq to:

  • listen to our IP address for DNS queries,
  • and to resolve DNS requests for our TLD to this address
sudo tee /etc/NetworkManager/dnsmasq.d/90-docker.conf <<'EOF'
listen-address=10.254.254.254
address=/.test/10.254.254.254
EOF

Configure docker DNS

Edit /etc/docker/daemon.json to tell the docker daemon to use our setup for DNS:

{
  "dns": [
    "10.254.254.254"
  ]
}

Reload services

systemctl reload NetworkManager docker

Sanity checks

# Check we didn't break global DNS:
nslookup example.com
docker run -it --rm alpine nslookup example.com

# Check that docker containers can resolve our domains:
docker run -it --rm alpine nslookup example.test
# Does it also work for internal networks?
docker network create --internal example
docker run -it --rm --net example alpine nslookup example.test
docker network rm example

DNS setup for macOS

Create a routable loopback IP

sudo tee /Library/LaunchDaemons/org.localhost.alias.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
      <string>org.localhost.alias</string>
    <key>RunAtLoad</key>
      <true/>
    <key>ProgramArguments</key>
      <array>
        <string>/sbin/ifconfig</string>
        <string>lo0</string>
        <string>alias</string>
        <string>10.254.254.254</string>
      </array>
  </dict>
</plist>
EOF
sudo launchctl load -w /Library/LaunchDaemons/org.localhost.alias.plist

Seup dnsmasq

Install via homebrew

brew install dnsmasq

We need to tell dnsmasq to:

  • listen to our IP address for DNS queries,
  • and to resolve DNS requests for our TLD to this address
tee "$(brew --prefix)/etc/dnsmasq.conf" <<'EOF'
bind-interfaces
listen-address=127.0.0.1
EOF
tee "$(brew --prefix)/etc/dnsmasq.d/docker.conf" <<'EOF'
listen-address=10.254.254.254
address=/.test/10.254.254.254
EOF

Configure macOS to launch dnsmasq at startup and load it now:

sudo brew services start dnsmasq

Create a macOS DNS resolver for .test domains.

sudo mkdir -p /etc/resolver
sudo tee /etc/resolver/test <<'EOF'
nameserver 127.0.0.1
EOF
sudo killall -HUP mDNSResponder

Configure docker DNS

Edit ~/.docker/daemon.json to tell the docker daemon to use our setup for DNS:

{
  "dns": [
    "10.254.254.254"
  ]
}

And restart docker.

Sanity checks

# Check that our macOS resolver works
scutil --dns
dscacheutil -q host -a name example.test

# Check we didn't break global DNS:
dscacheutil -q host -a name example.com
docker run -it --rm alpine nslookup example.com

# Check that docker containers can resolve our domains:
docker run -it --rm alpine nslookup example.test
# Does it also work for internal networks?
docker network create --internal example
docker run -it --rm --net example alpine nslookup example.test
docker network rm example
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment