How to Deploy a Tailscale Subnet Router and Exit Node in Docker for Secure Remote Access

Network architect configuring Tailscale subnet router and exit node in Docker for secure remote access
Bridge your local home or office network and route all internet traffic securely using a containerized Tailscale node.

Connecting to homelab hardware, internal office servers, and private hypervisor consoles while traveling typically requires maintaining a complex VPN gateway like OpenVPN or standard WireGuard. However, running traditional point-to-site VPNs forces administrators to open public firewall ports, manage dynamic DNS hostnames, navigate double-NAT hurdles, and configure per-client cryptographic keyrings manually.

Tailscale transforms this paradigm by establishing a zero-config, peer-to-peer mesh overlay (known as a tailnet) built on top of the battle-tested WireGuard protocol. While installing the Tailscale client on every workstation and smartphone is straightforward, many critical devices—such as network switches, IPMI/iDRAC management controllers, IoT appliances, and legacy NAS units—cannot run third-party VPN binaries. This is where a Tailscale Subnet Router and Exit Node deployed inside Docker becomes invaluable.

By deploying a single, lightweight Tailscale container on an always-on Linux server or NAS, you can securely advertise your entire local subnet (e.g., 192.168.1.0/24) to authorized devices on your tailnet without opening a single incoming port on your home router. Furthermore, configuring the container as an Exit Node enables you to route all public internet traffic through your trusted home connection when connected to untrusted public Wi-Fi networks abroad.

Architectural Overview: Mesh Routing Without Port Forwarding

Unlike traditional hub-and-spoke VPNs where all traffic bottlenecks through a central gateway server, Tailscale creates direct peer-to-peer WireGuard tunnels between authenticated devices using NAT traversal techniques (STUN/DERP).

+-----------------------------------------------------------------------------------+
| REMOTE TAILSCALE CLIENT                                                           |
| (Laptop / Smartphone on Public Wi-Fi)                                             |
| Tailscale IP: 100.64.0.5                                                          |
+-----------------------------------------------------------------------------------+
                                         |
                                         | Peer-to-Peer Encrypted WireGuard Tunnel
                                         | (Zero Open Ports / Traverses NAT)
                                         v
+-----------------------------------------------------------------------------------+
| LOCAL HOMELAB / OFFICE DOCKER HOST                                                |
|                                                                                   |
|  +-----------------------------------------------------------------------------+  |
|  | TAILSCALE CONTAINER (Subnet Router & Exit Node)                             |  |
|  |                                                                             |  |
|  |  - Tailscale IP: 100.64.0.10                                                |  |
|  |  - Host Network Mode (Direct Access to eth0 / 192.168.1.50)                 |  |
|  |  - Linux Kernel TUN Device (/dev/net/tun)                                   |  |
|  |  - Advertises Subnet: 192.168.1.0/24                                        |  |
|  |  - Advertises Exit Node: 0.0.0.0/0, ::/0                                    |  |
|  +-----------------------------------------------------------------------------+  |
|                                        |                                          |
|                                        | IP Forwarding / Masquerade (iptables)   |
|                                        v                                          |
|  +-----------------------------------------------------------------------------+  |
|  | PRIVATE LAN SUBNET (192.168.1.0/24)                                         |  |
|  |                                                                             |  |
|  |  +-------------------+  +-------------------+  +-------------------------+  |  |
|  |  | TrueNAS Storage   |  | Proxmox Console   |  | Network Managed Switch  |  |  |
|  |  | 192.168.1.100     |  | 192.168.1.200     |  | 192.168.1.2             |  |  |
|  |  +-------------------+  +-------------------+  +-------------------------+  |  |
|  +-----------------------------------------------------------------------------+  |
+-----------------------------------------------------------------------------------+

When your remote device sends a packet addressed to 192.168.1.100, your local Tailscale client encapsulates that packet inside the encrypted WireGuard tunnel, forwards it directly to the containerized subnet router, and the container forwards it across the physical LAN interface. The target device responds normally, completely unaware that the traffic originated hundreds of miles away.

Prerequisites and Kernel Configuration

To act as a router, the underlying Linux kernel on your host machine must be configured to allow packet forwarding between network interfaces. Without this setting, the kernel silently discards packets destined for other subnets.

Log in to your host server (Ubuntu, Debian, or Raspberry Pi OS) and enable IPv4 and IPv6 forwarding:

# Check current forwarding status
sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding

# Enable forwarding persistently in sysctl
sudo tee /etc/sysctl.d/99-tailscale.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF

# Apply settings immediately
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

Verify that the Linux TUN device exists and is accessible:

ls -la /dev/net/tun

If the device node is missing, create it using:

sudo mkdir -p /dev/net
sudo mknod /dev/net/tun c 10 200
sudo chmod 0666 /dev/net/tun

Step 1: Generating a Tailscale Auth Key with Tags

To automate container startup without requiring an interactive web browser login each time the container restarts, generate an Auth Key in the Tailscale Admin Console.

  1. Navigate to the Tailscale Admin Console > Settings > Keys.
  2. Click Generate auth key.
  3. Enable Reusable (recommended if you recreate containers during upgrades).
  4. Assign an ACL Tag, such as tag:subnet-router. When an auth key carries a tag, Tailscale automatically disables key expiration for the device, preventing unexpected disconnects months later.
  5. Copy the generated key (formatted as tskey-auth-...).

Step 2: Preparing the Docker Environment

Create a dedicated directory structure on your Docker host to store the persistent state files (such as node identity keys and local state databases):

sudo mkdir -p /opt/tailscale-router/state
cd /opt/tailscale-router
sudo chmod 700 state

Create an environment file /opt/tailscale-router/.env containing your sensitive configuration parameters:

# Tailscale Configuration
TS_AUTH_KEY=tskey-auth-kXXXXX-XXXXXXXXXXXXXXXXXXXXXXXX
TS_HOSTNAME=homelab-subnet-router
TS_ROUTES=192.168.1.0/24

Secure the environment file permissions:

sudo chmod 600 /opt/tailscale-router/.env

Step 3: Creating the Production Docker Compose Manifest

A subnet router requires direct access to host network interfaces and elevated network capabilities (NET_ADMIN and NET_RAW) to manipulate routing tables and bind directly to the local LAN.

Create docker-compose.yml in /opt/tailscale-router/docker-compose.yml:

services:
  tailscale:
    image: tailscale/tailscale:v1.78.2
    container_name: tailscale-subnet-router
    hostname: ${TS_HOSTNAME}
    network_mode: host
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - NET_RAW
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
      - TS_AUTHKEY=${TS_AUTH_KEY}
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=false
      - TS_ROUTES=${TS_ROUTES}
      - TS_EXTRA_ARGS=--advertise-exit-node --accept-dns=false --snat-subnet-routes=true
    volumes:
      - ./state:/var/lib/tailscale
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Let us analyze the critical configuration flags:

  • network_mode: host: Connects the container directly to the host’s network stack, allowing it to route traffic directly across physical Ethernet adapters without secondary Docker NAT translation layers.
  • TS_USERSPACE=false: Instructs Tailscale to utilize the high-performance Linux kernel WireGuard implementation via /dev/net/tun rather than slower userspace routing.
  • TS_ROUTES=${TS_ROUTES}: Specifies the internal IP ranges to advertise across the tailnet (e.g., 192.168.1.0/24).
  • --advertise-exit-node: Enables the container to advertise full default route capabilities (0.0.0.0/0 and ::/0).
  • --snat-subnet-routes=true: Automatically translates source IP addresses (Source NAT / Masquerade) for outgoing packets onto the physical LAN. This prevents asymmetric routing issues where LAN devices might attempt to respond via the default router rather than back through the Tailscale node.

Step 4: Launching and Authorizing the Router

Launch the container in detached mode:

docker compose up -d

Monitor container logs to verify successful authentication with the coordination servers:

docker logs -f tailscale-subnet-router

You should see confirmation messages indicating login and route advertisement:

[+] Logged in as tag:subnet-router
[+] IP: 100.64.0.10
[+] Node identity saved to /var/lib/tailscale/tailscaled.state
[+] Advertising routes: 192.168.1.0/24
[+] Advertising exit node capability

Step 5: Approving Subnet Routes in the Tailscale Console

For security, newly advertised subnet routes and exit node capabilities are placed in a pending state until explicitly authorized by a tailnet administrator.

  1. Open the Tailscale Machines Console.
  2. Locate your new machine: homelab-subnet-router.
  3. Notice the badge indicating Subnets and Exit Node.
  4. Click the three dots (...) menu on the right and select Edit route settings.
  5. Check the box next to your advertised subnet (192.168.1.0/24) to approve routing.
  6. Check the box next to Use as exit node to allow devices to route internet traffic through this server.
  7. Click Save.

Step 6: Configuring Tailscale Access Control Lists (ACLs)

Tailscale allows administrators to define declarative JSON-based Access Control Lists (ACLs). This ensures that only designated users can access sensitive internal subnets.

In the Tailscale Admin Console under Access Controls, you can add granular rules restricting subnet access to specific groups or users:

{
  "tagOwners": {
    "tag:subnet-router": ["autogroup:admin"]
  },
  "acls": [
    // Allow admins full access to internal homelab subnet
    {
      "action": "accept",
      "src":    ["group:admin"],
      "dst":    ["192.168.1.0/24:*", "autogroup:internet:*"]
    },
    // Allow standard family/team members only exit node access
    {
      "action": "accept",
      "src":    ["group:users"],
      "dst":    ["autogroup:internet:*"]
    }
  ]
}

Step 7: Testing Remote Connectivity from Client Devices

Once approved, remote devices on your tailnet can access your LAN resources seamlessly.

1. Testing Subnet Access (Desktop / Mobile):
Disconnect your laptop or mobile phone from your local Wi-Fi (e.g., connect to cellular data or a mobile hotspot). Open a browser and navigate directly to your local IP addresses, such as https://192.168.1.200:8006 for Proxmox or http://192.168.1.100 for TrueNAS. The web console will load instantly over the encrypted mesh tunnel.

2. Testing the Exit Node:
In the Tailscale client application on your phone or laptop:

  • Click the Tailscale menu icon.
  • Select Exit Nodes > homelab-subnet-router.
  • Toggle Run exit node on.
  • Visit an IP check website (like ifconfig.me). The reported public IP address will now match your home or office broadband connection instead of your local cellular or hotel Wi-Fi provider.

Troubleshooting Common Failure Modes

1. Packets Reach Subnet Router but LAN Devices Do Not Respond
Cause: Many consumer routers and managed switches discard reply packets originating from unexpected Tailscale IP ranges (e.g., 100.64.0.0/10) because they lack a static routing entry back to the Docker host.
Fix: Ensure --snat-subnet-routes=true is set in TS_EXTRA_ARGS inside your docker-compose.yml. This rewrites the packet source to the Docker host’s physical LAN IP, ensuring seamless layer-3 replies.

2. Container Reboots and Re-Registers as a Duplicate Device
Cause: The container’s state directory was not mounted to a persistent volume, causing the daemon to generate a new cryptographic machine key on each restart.
Fix: Verify that ./state:/var/lib/tailscale is mounted to a persistent host directory and ensure TS_STATE_DIR=/var/lib/tailscale is explicitly declared.

3. Exit Node Routing Breaks DNS Resolution on Clients
Cause: MagicDNS conflicts with local ISP DNS overrides when full tunnel routing is enabled.
Fix: In the Tailscale Admin Console under DNS, configure trusted upstream resolvers (such as Quad9 9.9.9.9 or Cloudflare 1.1.1.1) and enable Override local DNS.

Conclusion and Next Steps

Deploying a Tailscale Subnet Router and Exit Node in Docker provides a rock-solid, zero-trust remote access solution without the security risks of public port forwarding or dynamic DNS tracking. By encapsulating traffic in peer-to-peer WireGuard connections, you maintain instant, secure access to your entire homelab or office infrastructure from anywhere in the world.

To further enhance your setup, consider integrating MagicDNS with a self-hosted Pi-hole or AdGuard Home DNS server to enjoy network-wide ad blocking and private split-horizon domain resolution across all your remote endpoints.