When I started self-hosting services like Jellyfin and Whoogle, they were all accessible at IP:port, which isn't always easy to memorize or type. Even if you use Tailscale, you might get the benefit of MagicDNS, which lets you replace the IP with something like "MyNAS", but you still need to have all the ports memorized. At first I just bookmarked everything so I didn't have to memorize any ports. But I really wanted to be able to do something like jellyfin.home or whoogle.home.

I didn't want or need anything to be accessible with a public domain. I just wanted to be able to access my own local services at a made up address (I chose .home), whether I was home or not.

So here's how I did that.

Basically I needed 3 things:

  1. A reverse proxy
  2. A way for DNS to handle the .home address
  3. Tailscale for remote access

All of my services are hosted on my NAS with Docker, and Caddy-Docker-Proxy makes it really easy to set up a reverse proxy for each container. After installing Caddy-Docker-Proxy, you just need to add the caddy label to each Docker container.

services:
  jellyfin:
    container_name: jellyfin
    image: jellyfin/jellyfin
    restart: unless-stopped
    networks:
      - caddy
    labels:
      caddy: http://jellyfin.home
      caddy.reverse_proxy: "{{upstreams 8096}}"
    # network_mode: host
    environment:
      - TZ=America/Chicago
      - PUID=1000
      - PGID=10
      # - NVIDIA_DRIVER_CAPABILITIES=all
      # - NVIDIA_VISIBLE_DEVICES=all
    volumes:
      - "/volume1/jellyfin/config:/config"
      - "/volume1/jellyfin/config/cache:/cache"
      - "/volume1/jellyfin/config/log:/logs"
      # Media folders
      - "/volume1/jellyfin/Movies:/movies"
      - "/volume1/jellyfin/Shows:/shows"
      - "/volume1/jellyfin/Home Videos:/homevideos"
      - "/volume1/jellyfin/YouTube:/youtube"
      - "/volume1/jellyfin/YouTube Music:/youtube_music"
      - "/volume1/jellyfin/Music:/music"
      - "/volume1/jellyfin/Music Videos:/music_videos"
    devices:
      - /dev/dri:/dev/dri
networks:
  caddy:
    external: true

That's the reverse proxy portion. Next, I needed to tell my browser what to do with the .home domain. I use AdGuard Home for ad/tracker blocking, which is a separate topic. But AdGuard Home has a feature called "DNS Rewrite" which lets you point domains to a custom IP address. In order to get everything working, I had to create a wildcard DNS record that points *.home to my NAS's Tailscale IP. After doing that, I can go to jellyfin.home, for example, no matter where I am, and it works.

So that's it. AdGuard Home handles the DNS, so anything that ends with .home resolves to my NAS's Tailscale IP (so it works no matter where I am), and then Caddy-Docker-Proxy makes sure I reach the port I'm looking for.

Any time I add a new service with Docker, I just add the caddy label to the compose file, and it works immediately. Here's another example below. You'll notice that I comment out the port, because it's not necessary anymore. Also note that the port you use for "upstreams" is the second number after the colon (the container port, not the host port). So 9090, not 9080.

services:
  linkding:
    container_name: linkding
    image: sissbruecker/linkding:latest
    networks:
      - caddy
    labels:
      caddy: http://linkding.home
      caddy.reverse_proxy: "{{upstreams 9090}}"
    # ports:
      # - "9080:9090"
    volumes:
      - "/volume1/docker/linkding/data:/etc/linkding/data"
    env_file:
      - .env
    restart: unless-stopped
networks:
  caddy:
    external: true