tailscale-deploy

heimeshoff's avatarfrom heimeshoff

Deploy F# applications with Tailscale sidecar for private network access.Use when: "deploy", "production", "Tailscale", "docker-compose", "Docker","Portainer", "private network", "deployment", "ship it", "go live","home server", "self-host", "container", "release".Creates docker-compose.yml with app + Tailscale sidecar for secure access.No public ports, no authentication needed - Tailscale handles it.

⭐1stars🔀0forks📁View on GitHub🕐Updated Dec 24, 2025

When & Why to Use This Skill

This Claude skill automates the secure deployment of F# applications using a Tailscale sidecar pattern within Docker Compose. It enables developers to establish private network access for their containers without exposing public ports, leveraging WireGuard encryption to simplify remote access to home servers, Portainer stacks, and production environments.

Use Cases

  • Deploying F# web applications to a home server or private cloud while maintaining secure remote access without the need for complex port forwarding or firewall rules.
  • Setting up internal-only corporate tools or dashboards that require identity-based access control provided by Tailscale's networking layer.
  • Generating production-ready docker-compose.yml configurations that include automated VPN sidecars, health checks, and persistent data volume management for F# projects.
  • Streamlining the 'Ship It' process for developers who need to move from local F# development to a secured, networked environment with minimal manual configuration.
nametailscale-deploy
description|
Use when"deploy", "production", "Tailscale", "docker-compose", "Docker",
allowed-toolsRead, Edit, Write, Grep, Bash

Tailscale Sidecar Deployment

When to Use This Skill

Activate when:

  • User requests "deploy to production", "set up Tailscale"
  • Need to create Docker deployment configuration
  • Setting up private networking for F# application
  • Deploying to Portainer or home server
  • User mentions "docker-compose", "deployment", or "Tailscale"

Prerequisites

  • Docker and Docker Compose installed
  • Tailscale account (https://tailscale.com)
  • F# application with Dockerfile (multi-stage build)
  • Target deployment server (Linux recommended)

Architecture

Internet
    ↓ (blocked - no public ports)
Tailscale Network (WireGuard encrypted)
    ↓
Tailscale Sidecar Container
    ↓ (internal network)
F# Application Container

Key Benefits:

  • No public ports exposed
  • No authentication needed (Tailscale handles it)
  • Access from any device on your Tailnet
  • Encrypted WireGuard connections
  • Device authorization at network level

docker-compose.yml

Location: Project root

version: '3.8'

services:
  # F# Application
  app:
    build: .
    container_name: my-fsharp-app
    restart: unless-stopped
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ASPNETCORE_URLS=http://+:5000
    volumes:
      # Persist SQLite database and files
      - ./data:/app/data
    networks:
      - app-network
    depends_on:
      - tailscale
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s

  # Tailscale Sidecar
  tailscale:
    image: tailscale/tailscale:latest
    container_name: my-fsharp-app-tailscale
    hostname: my-fsharp-app
    restart: unless-stopped
    environment:
      - TS_AUTHKEY=${TS_AUTHKEY}
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_HOSTNAME=my-fsharp-app
      - TS_ACCEPT_DNS=true
      - TS_USERSPACE=false
    volumes:
      - tailscale-data:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "tailscale", "status"]
      interval: 30s
      timeout: 5s
      retries: 3

networks:
  app-network:
    driver: bridge

volumes:
  tailscale-data:

Configuration Notes:

  • hostname - Name that appears in Tailscale admin console
  • TS_HOSTNAME - DNS name on Tailnet (access via http://my-fsharp-app)
  • TS_AUTHKEY - One-time auth key from Tailscale admin
  • /dev/net/tun - Required for Tailscale networking
  • cap_add - Required Linux capabilities for VPN

Environment Configuration

.env File

Location: Project root (same directory as docker-compose.yml)

# Tailscale authentication key
TS_AUTHKEY=tskey-auth-xxxxxxxxxxxxx

# Application settings (optional)
ASPNETCORE_ENVIRONMENT=Production

Important:

  • Add .env to .gitignore (never commit secrets)
  • Auth key is one-time use
  • Generate new key for each deployment

Generate Tailscale Auth Key

  1. Go to https://login.tailscale.com/admin/settings/keys
  2. Click "Generate auth key"
  3. Settings:
    • ✅ Reusable (if deploying multiple times)
    • ✅ Ephemeral (device removed when container stops)
    • Set expiration (recommend 90 days)
  4. Copy key to .env file

Deployment Steps

Local Development Testing

# 1. Build application
dotnet build

# 2. Build Docker image
docker build -t my-fsharp-app:latest .

# 3. Start stack
docker-compose up -d

# 4. Check logs
docker-compose logs -f

# 5. Verify Tailscale connection
docker exec my-fsharp-app-tailscale tailscale status

# 6. Access application
# Open browser: http://my-fsharp-app (from any device on Tailnet)

Production Deployment (Portainer)

Option 1: Using Portainer UI

  1. Login to Portainer (usually http://server:9000)
  2. Go to Stacks → Add stack
  3. Name: my-fsharp-app
  4. Build method: Web editor
  5. Paste docker-compose.yml content
  6. Environment variables:
    • Add: TS_AUTHKEY = tskey-auth-xxxxx
  7. Click Deploy the stack

Option 2: Using Git Repository

  1. Push docker-compose.yml to Git repository
  2. In Portainer: Stacks → Add stack
  3. Build method: Repository
  4. Enter Git URL and branch
  5. Set environment variables
  6. Enable Automatic updates (optional)
  7. Click Deploy the stack

Verify Deployment

# Check container status
docker ps

# Check app logs
docker logs my-fsharp-app

# Check Tailscale logs
docker logs my-fsharp-app-tailscale

# Verify Tailscale connection
docker exec my-fsharp-app-tailscale tailscale status

# Check health
docker inspect my-fsharp-app | grep -A 5 Health

# Test app endpoint
curl http://localhost:5000/health  # From server
curl http://my-fsharp-app/health    # From Tailnet device

Accessing the Application

From Tailnet Devices

Any device on your Tailnet can access:

http://my-fsharp-app
http://my-fsharp-app:5000

Install Tailscale on devices:

From Server (Local)

# Use localhost
curl http://localhost:5000

# Or use Tailscale hostname
curl http://my-fsharp-app:5000

Updating the Application

Update Code and Redeploy

# 1. Pull latest code
git pull origin main

# 2. Rebuild Docker image
docker-compose build

# 3. Restart containers
docker-compose up -d

# 4. Check logs
docker-compose logs -f app

In Portainer

  1. Go to Stacks → your stack
  2. Click Editor
  3. Click Update the stack
  4. Select Re-pull image and redeploy
  5. Click Update

Troubleshooting

Tailscale Connection Issues

# Check Tailscale status
docker exec my-fsharp-app-tailscale tailscale status

# Check Tailscale logs
docker logs my-fsharp-app-tailscale

# Verify /dev/net/tun exists
ls -la /dev/net/tun

# Restart Tailscale container
docker restart my-fsharp-app-tailscale

# Check network capabilities
docker exec my-fsharp-app-tailscale ip addr show

Common Issues:

Issue Solution
/dev/net/tun not found Host needs TUN/TAP support: modprobe tun
Permission denied Container needs NET_ADMIN capability
Auth key expired Generate new key in Tailscale admin
Can't reach app Check app is running: docker logs my-fsharp-app

Application Issues

# Check app is running
docker ps | grep my-fsharp-app

# Check app logs
docker logs my-fsharp-app --tail 100

# Check app health
docker exec my-fsharp-app curl -f http://localhost:5000/health

# Check data volume
docker exec my-fsharp-app ls -la /app/data

# Restart app
docker restart my-fsharp-app

Network Issues

# Check network exists
docker network ls | grep app-network

# Inspect network
docker network inspect app-network

# Check containers can communicate
docker exec my-fsharp-app-tailscale ping -c 3 my-fsharp-app

Data Persistence Issues

# Check volume exists
docker volume ls | grep tailscale

# Check data directory
ls -la ./data

# Fix permissions (if needed)
sudo chown -R $(id -u):$(id -g) ./data

Multiple Applications

Deploy multiple F# apps on same server:

# app1/docker-compose.yml
services:
  app:
    container_name: todo-app
    # ... config
  tailscale:
    container_name: todo-app-tailscale
    hostname: todo-app
    environment:
      - TS_HOSTNAME=todo-app
    # ... config

# app2/docker-compose.yml
services:
  app:
    container_name: notes-app
    # ... config
  tailscale:
    container_name: notes-app-tailscale
    hostname: notes-app
    environment:
      - TS_HOSTNAME=notes-app
    # ... config

Access:

  • http://todo-app
  • http://notes-app

Best Practices

✅ Do

  • Use unique hostnames for each application
  • Set auth key expiration (security)
  • Use ephemeral keys for temporary deployments
  • Monitor Tailscale connection health
  • Keep Tailscale image updated
  • Persist data in volumes (not containers)
  • Use health checks
  • Test locally before production deployment

❌ Don't

  • Expose ports to public internet (defeats Tailscale purpose)
  • Commit .env file to Git
  • Reuse auth keys across environments
  • Run without NET_ADMIN capability
  • Forget to mount /dev/net/tun
  • Skip health checks
  • Deploy without testing locally first

Security Considerations

Tailscale Provides:

  • Network-level authentication (device authorization)
  • WireGuard encryption
  • Access control via Tailscale ACLs
  • Automatic certificate management

Application Still Needs:

  • Input validation (SQL injection, XSS prevention)
  • Authorization (who can do what in the app)
  • Secure data storage
  • Audit logging

Tailscale Admin:

  • Review connected devices regularly
  • Disable/remove unused devices
  • Use ACLs to restrict access by group
  • Enable MFA for Tailscale account

Verification Checklist

  • docker-compose.yml created
  • .env file created with TS_AUTHKEY
  • .env added to .gitignore
  • Tailscale auth key generated
  • Application Dockerfile exists
  • Data directory created
  • Containers start successfully
  • Tailscale shows connected
  • Application accessible via Tailscale hostname
  • Health checks passing
  • Logs show no errors
  • fsharp-feature - Complete application to deploy
  • fsharp-backend - Backend that gets deployed
  • fsharp-frontend - Frontend that gets deployed
  • /docs/07-BUILD-DEPLOY.md - Docker build and deployment guide
  • /docs/08-TAILSCALE-INTEGRATION.md - Detailed Tailscale integration
  • CLAUDE.md - Deployment model overview

Additional Resources