tailscale, caddy, and nixos containers - a match made in heaven

tailscale, caddy, and nixos containers - a match made in heaven

For a little while now I’ve been running some services (jellyfin etc.) on an old laptop in my house. I’m not trying to sound like a podcast ad but as a networking novice, the simplicity tailscale brings to accessing these services remotely is very nice. Until recently though, I had been accessing my services like a heathen with http and port numbers (eg http://tailscale-ip:service-port). This works and is perfectly secure thanks to tailscale though it lacks a certain finesse. In an ideal world you’d have a reverse proxy and set up SSL certs so your browser doesn’t get stressed and you dont have to rememeber ip addresses and port numbers.

When I initially looked at how to do this it seemed like it was above my paygrade and not worth the stress; that was until I came across this. This works great and is as simple as advertised though there is one drawback: you can only reverse proxy one service per host. So for my usecase of the laptop with multiple services running on it I could only use the magic caddy tailscale auto-https thing for one of them.

what to do?

Seeing as I was already using nixos on my latop server I turned to a slightly cumbersome nixos solution. One nixos-container for each service I wanted over https. I’d be lying If I said I completely understand all of this NAT business but this was the config I cobbled together (copied from the nixos docs).

  networking.nat = {
    enable = true;
    internalInterfaces = ["ve-+"];
    externalInterface = "ens3";
  };

  containers.jellyfin = {
    autoStart = true;
    enableTun = true;
    privateNetwork = true;
    hostAddress = "192.168.100.10";
    localAddress = "192.168.100.11";
    bindMounts = {
      "/films" = {
        hostPath = "/mnt/films";
      };
    };

    config = { pkgs, ... }: {
      
      services.tailscale = {
        enable = true;
        # permit caddy to get certs from tailscale
        permitCertUid = "caddy";
      };
      
      services.jellyfin = {
        enable = true;
        openFirewall = true;
      };

      services.caddy = {
        enable = true;
        extraConfig = ''

          jellyfin.tailnet-name.ts.net {
            reverse_proxy localhost:8096
          }

        '';
      };


      # open https port
      networking.firewall.allowedTCPPorts = [ 443 ];

      system.stateVersion = "23.05";

    };
  };
}

This example enables the jellyfin, tailscale, and caddy services, mounts a film folder from the host, and lets the container talk to the internet.

Once you’ve logged into the container sudo nixos-container root-login jellyfin and authenticated with tailscale sudo tailscale up, you should be able to access your jellyfin in your browser at https://jellyfin.tailnet-name.ts.net.

As well as solving the multiple services problem, separating services onto their own hosts is nice if you want to share a particular service with someone else. I personaly feel happier just sharing one container running jellyfin rather than the whole host with multiple things on it. Anyway thanks for listening to my TED talk.