Custom Local Domain over HTTPS
Note
This is a documention for my own reference, so I can come back to it in the future either for setting up a new custom local domain over HTTPS using docker
and nginx
, or reverting back to the original state.
Steps
mkcert
Install mkcert:
# MacOS
$ brew install mkcert
# Create and install a local certificate authority (CA), trusted locally on your device.
$ mkcert -install
# List the root CA files.
$ ls "$(mkcert -CAROOT)"
rootCA-key.pem rootCA.pem
# Create a trusted certificate.
$ mkdir potato_cld && cd potato_cld
$ mkcert potato.cld "*.potato.cld"
Created a new certificate valid for the following names 📜
- "potato.cld"
- "*.potato.cld"
Reminder: X.509 wildcards only go one level deep, so this won\'t match a.b.potato.cld
The certificate is at "./potato.cld+1.pem" and the key at "./potato.cld+1-key.pem" ✅
It will expire on 11 May 2026 🗓
The command mkcert potato.cld "*.potato.cld"
above does two things:
- Generates a certificate for the hostname you've specified
- Lets
mkcert
(that you've added as a local CA) sign this certificate.
Now, the certificate is ready and signed by a certificate authority that the browser trusts locally.
Configure DNS
1. Modify /etc/hosts
The simplest way to go about this is to update the /etc/hosts
as shown below,
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting. Do not change this entry.
##
127.0.0.1 localhost
255.255.255.255 broadcasthost
::1 localhost
127.0.0.1 potato.cld api.potato.cld api2.potato.cld
But that is too simple. Let's crank up the complexity so I can learn something new - dnsmasq
.
2. Install, configure and start the dnsmasq
server
dnsmasq
is a lightweight DNS, TFTP, PXE, router advertisement and DHCP server. It is intended to provide coupled DNS and DHCP service to a LAN.Dnsmasq accepts DNS queries and either answers them from a small, local, cache or forwards them to a real, recursive, DNS server. It loads the contents of /etc/hosts so that local hostnames which do not appear in the global DNS can be resolved and also answers DNS queries for DHCP configured hosts. It can also act as the authoritative DNS server for one or more domains, allowing local names to appear in the global DNS. ... source:
man dnsmasq
# Install
$ brew install dnsmasq
$ brew list dnsmasq
/opt/homebrew/Cellar/dnsmasq/2.90/.bottle/etc/dnsmasq.conf
/opt/homebrew/Cellar/dnsmasq/2.90/homebrew.dnsmasq.service
/opt/homebrew/Cellar/dnsmasq/2.90/homebrew.mxcl.dnsmasq.plist
/opt/homebrew/Cellar/dnsmasq/2.90/sbin/dnsmasq
/opt/homebrew/Cellar/dnsmasq/2.90/share/man/man8/dnsmasq.8
# Create a new directory and a `.conf` file:
$ mkdir -p /Users/bsm/Development/dnsmasq.d
$ touch /Users/bsm/Development/dnsmasq.d/dnsmasq.conf
We will now uncomment and update the value of conf-dir=
in /opt/homebrew/etc/dnsmasq.conf
to point to dnsmasq.conf
file we created above.
% cat $(brew --prefix)/etc/dnsmasq.conf | grep conf-dir
#conf-dir=/opt/homebrew/etc/dnsmasq.d
conf-dir=/Users/bsm/Development/dnsmasq.d/,*.conf
#conf-dir=/opt/homebrew/etc/dnsmasq.d,.bak
#conf-dir=/opt/homebrew/etc/dnsmasq.d/,*.conf
Next, edit /Users/bsm/Development/dnsmasq.d/dnsmasq.conf
and add the line address=/potato.cld/127.0.0.1
.
$ cat /Users/bensoorajmohan/Development/dnsmasq.d/dnsmasq.conf
address=/potato.cld/127.0.0.1
Now,
# start the `dnsmasq` service:
$ sudo brew services start dnsmasq
Warning: Taking root:admin ownership of some dnsmasq paths:
/opt/homebrew/Cellar/dnsmasq/2.90/sbin
/opt/homebrew/Cellar/dnsmasq/2.90/sbin/dnsmasq
/opt/homebrew/opt/dnsmasq
/opt/homebrew/opt/dnsmasq/sbin
/opt/homebrew/var/homebrew/linked/dnsmasq
This will require manual removal of these paths using `sudo rm` on
brew upgrade/reinstall/uninstall.
==> **Successfully started `dnsmasq` (label: homebrew.mxcl.dnsmasq)**
# verify that the service is running:
$ sudo brew services
**Name Status User File**
black none
dnsmasq started root /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
unbound none
3. Configure resolv.conf
Configure the DNS server to be used for the domain potato.cld
:
# Make a new folder called /etc/resolver/, if it doesn't exist
$ sudo mkdir -p /etc/resolver/
# Create a new file with the name of the domain you want custom DNS settings for
$ touch /etc/resolver/potato.cld
# Set the nameserver that the domain should resolve to
$ sudo tee /etc/resolver/potato.cld >/dev/null <<EOF
nameserver 127.0.0.1
EOF
# restart local dnsmasq service
$ sudo brew services restart dnsmasq
Stopping `dnsmasq`... (might take a while)
==> **Successfully stopped `dnsmasq` (label: homebrew.mxcl.dnsmasq)**
Warning: Taking root:admin ownership of some dnsmasq paths:
/opt/homebrew/Cellar/dnsmasq/2.90/sbin
/opt/homebrew/Cellar/dnsmasq/2.90/sbin/dnsmasq
/opt/homebrew/opt/dnsmasq
/opt/homebrew/opt/dnsmasq/sbin
/opt/homebrew/var/homebrew/linked/dnsmasq
This will require manual removal of these paths using `sudo rm` on
brew upgrade/reinstall/uninstall.
==> **Successfully started `dnsmasq` (label: homebrew.mxcl.dnsmasq)**
Verify
$ dscacheutil -q host -a name potato.cld
name: potato.cld
ip_address: 127.0.0.1
# Verify the new resolver was picked up
$ scutil --dns
DNS configuration
resolver #8
domain : potato.cld
nameserver[0] : 127.0.0.1
flags : Request A records, Request AAAA records
reach : 0x00030002 (Reachable,Local Address,Directly Reachable Address)
Docker and Nginx
Clone the git repository and start the docker containers:
$ git clone https://github.com/bensooraj/blog-artefact-custom-local-domain.git
$ docker-compose up -d
$ curl https://api.potato.cld/ok
200 OK: api.potato.cld
$ curl https://api2.potato.cld/ok
200 OK: api2.potato.cld
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ef88c504a751 nginx:latest "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp nginx
39651f26920c one_tls_whoami-goapi "./server" 2 minutes ago Up 2 minutes 8080/tcp, 0.0.0.0:8080->443/tcp one_tls_whoami-goapi-1
a98caad7d4cb one_tls_whoami-goapi2 "./server" 2 minutes ago Up 2 minutes 8080/tcp, 0.0.0.0:8081->443/tcp one_tls_whoami-goapi2-1
$ docker logs nginx --tail=2
192.168.65.1 - - [15/Feb/2024:09:54:17 +0000] "GET /ok HTTP/1.1" 200 23 "-" "curl/8.1.2" "-"
192.168.65.1 - - [15/Feb/2024:09:54:18 +0000] "GET /ok HTTP/1.1" 200 24 "-" "curl/8.1.2" "-"
$ docker logs one_tls_whoami-goapi-1 --tail=2
2024/02/15 09:54:09 INFO server started on localhost. protocol=https port=443
2024/02/15 09:54:17 INFO Header: X-API-ServerName=api.potato.cld
$ docker logs one_tls_whoami-goapi2-1 --tail=2
2024/02/15 09:54:09 INFO server started on localhost. protocol=https port=443
2024/02/15 09:54:18 INFO Header: X-API-ServerName=api2.potato.cld
Explore the nginx
configuration files:
$ cat nginx/conf/api2_potato_cld.conf
server {
listen 80;
listen [::]:80;
server_name api2.potato.cld;
return 301 https://api2.potato.cld$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name api2.potato.cld;
ssl_certificate /etc/nginx/ssl/potato.cld.pem;
ssl_certificate_key /etc/nginx/ssl/potato.cld-key.pem;
# If they come here using HTTP, bounce them to the correct scheme
error_page 497 https://$server_name:$server_port$request_uri;
error_log /dev/stderr;
access_log /dev/stdout main;
location / {
proxy_set_header X-API-ServerName $server_name;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_pass https://host.docker.internal:8081;
}
}
References
- How to use HTTPS for local development
- macOS: Using Custom DNS Resolvers
- Mastering NGINX Logs in Docker: From Novice to Ninja
- How to create local wildcard domains in MacOS using dnsmasq?
- Gist: Never touch your local /etc/hosts file in OS X again
- OS/X "etc/resolver/dev" isn't working - why not?
- Using Dnsmasq for local development on OS X
- Local domains with HTTPS and Docker compose
- Docker-Powered Web Development Utilizing HTTPS and Local Domain Names
- Setting up a custom domain for your local apps (Mac OS & Linux)
- Configure NGINX as a Reverse Proxy with Docker Compose file
- How to Setup a Local DNS Server Using DNSMasq