Exposing Local Development Servers Securely with Tailscale Serve

Sometimes you need a local service reachable from another device, maybe to test something on a phone, show work-in-progress to a teammate, or run an integration test across machines. Tools like ngrok have been the go-to solution, but there’s a simpler option if you already use Tailscale: Tailscale Serve.

This post covers how I used Tailscale Serve to make a local Dockerized Rails + Ember setup available from my laptop, without editing my docker-compose.yml.

The Problem: Ports Bound to 127.0.0.1

Take this example docker-compose.yml snippet:

1
2
3
4
5
6
7
services:
  api:
    ports:
      - "127.0.0.1:3000:3000"
  ember:
    ports:
      - "127.0.0.1:4200:4200"

Both services are bound to localhost only. That means they’re accessible from the host machine, but not from your LAN or other devices on your Tailnet. Changing to 0.0.0.0:3000 would solve that, but sometimes you don’t want to (or can’t) touch your compose files.

The Tailscale Serve Solution

Tailscale Serve lets you proxy a port from your machine to the rest of your Tailnet without changing your app’s binding. Think of it as a built-in, self-hosted ngrok.

Step 1: Install and log into Tailscale

On your dev machine (e.g., Arch Linux host):

1
2
3
sudo pacman -S tailscale
sudo systemctl enable --now tailscaled
sudo tailscale up

Do the same on your macOS laptop (download from tailscale.com/download).

Step 2: Serve your local services

Forward your Rails app running on localhost:3000:

1
sudo tailscale serve --tcp 3000 tcp://localhost:3000

And your Ember app on port 4200:

1
sudo tailscale serve --tcp 4200 tcp://localhost:4200

Step 3: Access from another device

On your Mac (or any device logged into the same tailnet):

  • Use the Tailscale IP: http://100.x.x.x:3000
  • Or, if you’ve enabled MagicDNS, use the hostname: http://archmachine.tailnet-name.ts.net:3000

That’s it: no config changes, no firewall rules, no router port forwards.

Going Further: TLS & Public Sharing

  • Want HTTPS? Tailscale can terminate TLS for you:
1
sudo tailscale serve --tls-terminated-tcp 443 tcp://localhost:3000

Now you can visit https://archmachine.tailnet-name.ts.net/ with a valid cert.

  • Want to share outside your tailnet? Tailscale’s Funnel feature routes traffic from the public internet to your local service, with ACLs.

Why Tailscale Serve instead of ngrok?

You already use Tailscale for secure networking, so there are no extra accounts to manage. Traffic stays inside your Tailnet and end-to-end encrypted. MagicDNS gives you stable hostnames, and it works across every machine in your tailnet by default. There are no tunnels to spin up.

It’s basically an ngrok alternative that was already on your VPN.