Up until a few days ago, I used the Docker sidecar method to expose services with easy to remember addresses. For instance, using it with Immich so I could just enter something like https://immich.gallant-panda.ts.net in the browser to get to it. This works well but always seemed a bit clunky since it requires an additional docker container to work.

With the beta release of Tailscale services there is, in my opinion, a better, cleaner way to do this. Services allow you to essentially expose a specific service (not the whole machine) as a node on your Tailnet. You can even assign tags to it in order to apply access control via the normal Tailscale ACL’s and grants. The service can also act as a sort of load balancer since you can host the service on multiple machines and Tailscale will connect the user to the closest one automatically.

Setting up a Service#

Here’s how you set this up:

  1. Set up the service you want to access in Docker. You likely want to use Docker Compose for this.
  2. Ensure you can access the application via the port mapping and the host machine’s Tailnet name. For instance, if you host it on a machine called server1 and exposed port 8080 in the Docker compose file, make sure you can access the application via http://server1:8080.
  3. Click the Services tab at the top of the Tailscale admin console page.
  4. Click the “Define a Service” button.
  5. Enter a name for the service. If you named the service photos and your Tailnet name was gallant-panda.ts.net , you would access the service using photos.gallant-panda.ts.net.
  6. Enter a description.
  7. Enter a port. This should will almost always be 443 to specify access via TLS/SSL (using https:// in your browser).
  8. Select a tag if you’ve set one up to restrict access to the service.
  9. Click the “Define Service” button. At the bottom of the page it will indicate that it is waiting for a host to advertise the service. You’ll need to come back here and click the Approve button after completing the next section.

Prep the Machine for Hosting#

A machine that hosts a service must have a tag assigned to it. If you need to create and assign a tag:

  1. In the Tailscale admin console page, click the “Access Controls” tab.
  2. Click the “Visual editor” switch if it’s currently set to “JSON editor”.
  3. Click the Tag tab in the Access Controls section.
  4. Click “+ Create Tag”.
  5. Enter a tag name (it can only container letters, numbers, dashes, hyphens and periods).
  6. Select a Tag owner (probably want to use your email address).
  7. Enter notes if you want to.
  8. Click the “Save tag” button.
  9. Click the Machines tab at the top of the page.
  10. Click the “…” button at the end of the line showing the machine hosting the service.
  11. Click “Edit ACL Tags”.
  12. Select the tag from the “Add tags” dropdown.
  13. Click “Save”.

Hosting the Service#

To host the service:

  1. SSH to the host machine if it’s not the machine you’re currently working on.
  2. Run this command, replacing <svc-name> and <port> with your values.
sudo tailscale serve --service=svc:<svc-name> --https=443 127.0.0.1:<port>
  1. Using our running example, this would be:
sudo tailscale serve --service=svc:photos --https=443 127.0.0.1:8080
  1. Go back to the Tailscale admin console. Edit the service we defined in the first section and click the Approve button at the bottom to enable the host we just defined.

Access the Service#

In a browser, access the service using the Tailscale service name (e.g., https://photos.gallant-panda.ts.net). This may fail the first time as Tailscale needs to hit Let’s Encrypt to obtain an SSL certificate.

(Optional) Lock Down the Docker Port Mappings#

Now that we can access the application via the Tailscale service, you might want to lock down the port mappings of the Docker service to prevent access via the machine’s IP address. To do so modify the port mapping and restrict it to the local machine. So it you were exposing port 8080 via something like 8080:8080, change it to 127.0.0.1:8080:8080. This means that the port is only exposed for the local host and not for the whole network.

Conclusion#

Tailscale Services provides a clean approach to exposing your Docker services to your Tailnet. This is the method I’ll be using going forward.