Introduction

After getting used to the convenience of laravel/valet, in development environments built with Docker, we often need to set up local domain names for projects in the local /etc/hosts file. This leads to a problem where each person might configure different domain names, and team members often need to communicate and reproduce issues through URLs.

For example, if developer A gives the CRM project the domain name crm.localhost, and developer B gives it crm.dev, this results in needing to define a set of environments for each person in our shared API debugger. At the same time, when reproducing issues, it also increases communication costs.

To maintain environment consistency as much as possible, we will embed service access domain names in Docker Compose orchestration through configuration files. This way, everyone just needs to execute docker compose up -d to get a consistent development environment, including domain names and HTTPS certificates!

Selection

To achieve a highly unified vision, I researched the following open-source projects:

  • dnsmasq
  • CoreDNS
  • AdGuardDNS

Since laravel/valet also uses dnsmasq, I initially considered choosing dnsmasq, but after searching around, I couldn’t find an officially maintained Docker Image. There are some versions maintained by individual developers, but I decided against using them.

Later, I discovered that CoreDNS is developed in Go language and is a graduated project incubated by Cloud Native, so the future community support is definitely assured. After a simple trial, I found it basically meets our needs and supports wildcard resolution.

AdGuard Home was an unexpected find. I say this because it’s aimed at ordinary users rather than developers, providing a clean and beautiful management interface. However, it’s not quite suitable for us because it’s mainly used to block ads, and configuration requires logging into the admin backend to configure custom domain rewrites, though it also supports wildcard resolution.

After comparison, we finally chose CoreDNS, partly due to its stronger community, rich plugins, and ability to ensure consistency through configuration files.

Getting the Image

Because our team uses M1 Chips based on ARM architecture, and the official currently doesn’t provide a Docker Image for ARM architecture, we built our own ARM architecture Docker Image.

docker pull betterde/coredns

Other CPU architectures can pull the official CoreDNS Docker Image

Configuration

First, create a docker-compose.yml container orchestration configuration file:

services:
  coredns:
    image: betterde/coredns:latest
    ports:
      - 53:53/udp
    restart: always
    command: -conf /root/Corefile
    volumes:
      - ./config:/root
    hostname: coredns
    container_name: coredns

The new version of Docker Compose no longer requires defining version.

Next, create the CoreDNS configuration folder:

mkdir config

For example, if we use betterde.it as the TLD for our local development environment, create a Corefile configuration file in the folder:

.:53 {
    forward . 223.5.5.5 223.6.6.6
    log
    errors
}

betterde.it:53 {
    file /root/betterde.it.db
    log
    loop
    reload
    errors
}

Finally, create a data file betterde.it.db for defining domain resolution:

betterde.it.        IN  SOA dns.betterde.it. george.example.com. 2017042745 7200 3600 1209600 3600
*.betterde.it.      IN  A   127.0.0.1
dns.betterde.it.    IN  A   127.0.0.1
host.betterde.it.   IN  A   192.168.1.10
server.betterde.it. IN  CNAME   host.betterde.it.

If you’ve configured BIND domain server zones before, the betterde.it.db configuration file should be familiar. The george.example.com. is the administrator’s email, and since @ has other uses in zone configuration files, . is used as a replacement here.

Starting the Service

docker compose up -d

Verifying Results

dig @127.0.0.1 dns.betterde.it

; <<>> DiG 9.10.6 <<>> @127.0.0.1 dns.betterde.it
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37167
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;dns.betterde.it.			IN	A

;; ANSWER SECTION:
dns.betterde.it.		0	IN	A	127.0.0.1

;; Query time: 21 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon Nov 28 15:49:28 CST 2022
;; MSG SIZE  rcvd: 67

Using CoreDNS

On macOS, you can configure your own DNS through the following steps and management commands:

  • Check system DNS priority
scutil --dns
DNS configuration

resolver #1
  nameserver[0] : 223.6.6.6
  nameserver[1] : 202.46.34.75
  if_index : 14 (en0)
  flags    : Request A records
  reach    : 0x00000002 (Reachable)

resolver #2
  domain   : local
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300000

resolver #3
  domain   : 254.169.in-addr.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300200

resolver #4
  domain   : 8.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300400

resolver #5
  domain   : 9.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300600

resolver #6
  domain   : a.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300800

resolver #7
  domain   : b.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 301000

resolver #8
  domain   : it
  nameserver[0] : 127.0.0.1
  flags    : Request A records, Request AAAA records
  reach    : 0x00030002 (Reachable,Local Address,Directly Reachable Address)

DNS configuration (for scoped queries)

resolver #1
  nameserver[0] : 223.6.6.6
  nameserver[1] : 202.46.34.75
  if_index : 14 (en0)
  flags    : Scoped, Request A records
  reach    : 0x00000002 (Reachable)

We can see that it’s currently using Alibaba Cloud DNS 223.6.6.6 and Telecom 202.46.34.75 as DNS.

  • List all network connection methods:
networksetup -listallnetworkservices
An asterisk (*) denotes that a network service is disabled.
USB-LAN
Wi-Fi
Thunderbolt Bridge
iPhone USB
GAVPN
Tailscale Tunnel
  • Set DNS server for a specific network connection method
networksetup -setdnsservers Wi-Fi 127.0.0.1

For example, I’m connected via Wi-Fi, so I’m setting the DNS query for the Wi-Fi device to go through the local Loopback address 127.0.0.1.

  • Verify results
scutil --dns
DNS configuration

resolver #1
  nameserver[0] : 127.0.0.1
  flags    : Request A records, Request AAAA records
  reach    : 0x00030002 (Reachable,Local Address,Directly Reachable Address)

resolver #2
  domain   : local
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300000

resolver #3
  domain   : 254.169.in-addr.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300200

resolver #4
  domain   : 8.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300400

resolver #5
  domain   : 9.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300600

resolver #6
  domain   : a.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300800

resolver #7
  domain   : b.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 301000

resolver #8
  domain   : it
  nameserver[0] : 127.0.0.1
  flags    : Request A records, Request AAAA records
  reach    : 0x00030002 (Reachable,Local Address,Directly Reachable Address)

DNS configuration (for scoped queries)

resolver #1
  nameserver[0] : 127.0.0.1
  if_index : 14 (en0)
  flags    : Scoped, Request A records, Request AAAA records
  reach    : 0x00000000 (Not Reachable)

From the above results, we can see that it’s now using 127.0.0.1 as DNS.

Conclusion

dig www.baidu.com

; <<>> DiG 9.10.6 <<>> www.baidu.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40287
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.baidu.com.			IN	A

;; ANSWER SECTION:
www.baidu.com.		196	IN	CNAME	www.a.shifen.com.
www.a.shifen.com.	196	IN	A	180.101.49.13
www.a.shifen.com.	196	IN	A	180.101.49.14

;; Query time: 22 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon Nov 28 16:02:29 CST 2022
;; MSG SIZE  rcvd: 149

dig crm.betterde.it

; <<>> DiG 9.10.6 <<>> crm.crm.betterde.it
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28646
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;crm.crm.betterde.it.			IN	A

;; ANSWER SECTION:
crm.crm.betterde.it.		0	IN	A	127.0.0.1

;; Query time: 5 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon Nov 28 16:04:42 CST 2022
;; MSG SIZE  rcvd: 67

With this, a small but complete DNS setup is finished, and it will become a necessary infrastructure for our team collaboration!

If you need to stop using CoreDNS, you can use the following commands:

# Clear the device's current DNS settings
networksetup -setdnsservers Wi-Fi empty

# Check the device's current DNS settings
networksetup -getdnsservers Wi-Fi

Finally, clear the DNS cache

dscacheutil -flushcache

I hope this is helpful, Happy hacking…