skip to content
A cartoon mountain smiling at you WNDR

On Networking at Home & Building Home Labs

/ 6 min read

I’ve known about it for awhile but a few weeks I got the drive to actually try out Tailscale and was absolutely blown away with how easy it made building a private mesh network. Since, I’ve just been exploring it more and more (in both a professional as well as personal setting).

I started by just connecting personal devices and a VPS where my new project lives. This let me lock down the box and force SSH access over the Tailnet (Tailscale private network), which is nice. I also set it up on my AppleTV which serves as a permanent “Exit Node” for when I’m out and about on random wifi networks. ==The “Exit Node” basically serves as a exit point thru which all of your traffic (private or otherwise) is encrypted and funneled through.== This means if I’m at a coffee shop on wifi all of my internet traffic goes over the private network and “hits” the internet through the AppleTV on my network at home (so no one in the coffee shop can listen in). Pretty neat, huh?

After seeing people talk about NextDNS and Control-D on Mastodon I decided to see about connecting some DNS solution up to add another degree of security. I’ve actually been trying out the free tier of NextDNS with my kids devices for a couple of months but wanted a bit more control so was happy to see that Control D does offer quite a bit more control, seems to be much more actively developed then NextDNS, is at a similar price point and integrates directly with Tailscale.

Setup was easy:

  1. Setup a new endpoint in Control D, choosing “other router” as the type:
Setting up an endpoint in Control D
  1. In Tailscale DNS, scroll down to the Nameservers setting and choose ControlD and enter your Endpoint ID and check the toggle to “Override Local DNS”:
Setting up a global nameserver in Tailscale

That’s it. I immediately started seeing logs in the Control D activity log for all of the different devices connected to my Tailnet. It’s all pretty wild to me, in how smooth it works.

I then got to thinking about my project - no one needs access to that NodeAPI other than me and no one needs access to the Metabase install which I’m using to do analysis on the database other than me so what about locking access to my Tailnet? So I did.

This ended up being tricky because both of those services were running on the VPS and Tailscale seems to struggle a bit to locally proxy multiple services on a single endpoint (in my internet research I found lots of people struggling with it and no one really talking about how to make it work). I did figure out something though: you can directly add containers to your Tailnet by attaching the Tailscale image to the container. So here’s what I’m doing:

  1. My VPS was already on the Tailnet and the services were open to the world behind an NGINX reverse proxy. I ditched NGINX and installed a Caddy Docker Container without the Tailscale container so it is essentially attached to the VPS endpoint. I also closed the open ports. To get the Caddy container running, I followed the instructions here because Caddy needs to be built so that Cloudflare can negotiate the DNS challenges for Let’s Encrypt. You’ll also need your domains on Cloudflare (which sucks but it is what it is) and an API Token with access to Zone.Zone (edit) and Zone.DNS (edit). There are instructions in the linked article above but if you want me to write more about this, let me know. Oh and create CNAME records in your zone file and point them to the VPS Tailnet domain (something like <name>.<tnet>.ts.net).

  2. I containerized the Node App which meant making a Dockerfile in it’s directory that looks like this:

# Use an official Node.js runtime as the base image
FROM node:23
# Set the working directory in the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json to the container
COPY package*.json ./
# Install app dependencies
RUN npm install
# Copy the rest of the application files to the container
COPY . .
# Expose the port that the app will run on
EXPOSE <port>
# Command to start the application
CMD ["node", "<app>"]

and a docker-compose.yml that looks like this:

services:
ts-qs:
image: tailscale/tailscale:latest
container_name: <ts-name>
hostname: <name> # this will be the name in TS
environment:
- TS_AUTHKEY=<key>?ephemeral=false
- TS_EXTRA_ARGS=--advertise-tags=<ts-tags>
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
volumes:
- ${PWD}/<ts-name>/state:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
restart: unless-stopped
qs:
build: .
container_name: <name>
network_mode: service:<ts-name>
depends_on:
- <ts-name>
restart: unless-stopped
volumes:
ts-qs:
driver: local

I did the same with Metabase - you can find docker-compose.yml info about it on the Metabase website and then you just add in the Tailscale service, similar to the above docker-compose. Of note: make sure the Tailscale names are different because they are different containers. Also, make sure to include the network_mode: service:<ts-name> line as it’s necessary for the Tailscale magic.

  1. The Caddyfile. This is what will tell Caddy how to reverse proxy your stuff:
metabase.domain {
reverse_proxy <container name>:3000
tls {
dns cloudflare <Cloudflare Token Secret>
resolvers 1.1.1.1
}
}
project.domain {
reverse_proxy <tailnet.device-address.ts.net>:3001
tls {
dns cloudflare <Cloudflare Token Secret>
resolvers 1.1.1.1
}
}

I’ve changed my info above so make sure the metabase.domain and project.domain equal the CNAME records in Cloudflare. Also, make sure to include the resolvers 1.1.1.1 in the tls directive - I initially had issues (I think because of ControlD managing DNS resolution) so the 1.1.1.1 points the TLS challenge straight to Cloudflare. As you can see in the example, you can proxy directly to the hostname or the qualified Tailnet hostname.

Once that finishes you can start the Caddy container (if it’s not already running) or restart it with docker compose -f </path/to/docker-compose.yml> restart. Give it a few minutes for the SSL certificates to be acquired and then you should be off to the races with services using real domains but only accessible to you on your Tailnet.

I’ll admit - I got excited enough about it that I added in a Linkding install and a dash. install, each using the same method above and it worked great!

I don’t know if this has been interesting or helpful or instructive (or just boring) - Let me know though if you have any questions or would like more details about any of the above. Long story short though?

Tailscale, Good. Control D, Good. Home Lab? Gooood.

Joey From Friends (From here)