What is the Sidecar Pattern?
The sidecar pattern is a design approach where a helper container runs alongside your main application container, providing supporting functionality without modifying the application itself. In ScaleTail, the Tailscale container acts as a network sidecar, handling all secure networking while your application focuses solely on its core functionality.Architecture Overview
How It Works
Network Namespace Sharing
The key to the sidecar pattern is thenetwork_mode: service:tailscale directive:
When using
network_mode: service:tailscale, the application container shares the exact same network interface as the Tailscale container. This means:- Both containers use
127.0.0.1to communicate - Network requests from the application automatically route through Tailscale
- No application code changes are needed
Traffic Flow
Tailscale Container Receives
The Tailscale container receives the encrypted request through the VPN tunnel
Serve Configuration Routes
Based on the
serve.json configuration, Tailscale proxies the request to http://127.0.0.1:8096Application Responds
The application container (sharing the same network namespace) receives the request on port 8096 and responds
Real-World Example: Jellyfin
Let’s examine a complete ScaleTail configuration for Jellyfin to see the sidecar pattern in action.Complete compose.yaml
compose.yaml
Breaking Down the Configuration
- Tailscale Container
- Application Container
- Serve Configuration
The Tailscale container handles all networking:Key Components:
- hostname: How the service appears in your Tailnet
- devices: Access to TUN device for VPN tunneling
- cap_add: Elevated privileges for network configuration
Benefits of the Sidecar Pattern
Zero Application Changes
Your application runs unmodified. No need to install Tailscale inside the app container or change application code.
Separation of Concerns
Networking logic lives in the Tailscale container, while application logic stays in the app container.
Reusable Pattern
The same Tailscale sidecar configuration works for any application - just change the proxy port.
Independent Updates
Update Tailscale or your application independently without affecting the other.
Simplified Security
Security configurations (VPN, auth, ACLs) are isolated in one container.
Easy Debugging
Network issues can be debugged in the Tailscale container without touching the application.
Common Sidecar Configurations
Configuration 1: Simple Web Service (Port 80)
For services like Vaultwarden or Portainer running on port 80:Configuration 2: Custom Port Service
For services on custom ports (e.g., Portainer on 9000):Configuration 3: Exit Node (No Application)
For a Tailscale exit node without an application sidecar:Exit nodes don’t use the sidecar pattern since they don’t proxy to an application. They forward all internet traffic instead.
Advanced Patterns
Socket Sharing (Caddy + Tailscale)
Some applications like Caddy can use the Tailscale socket directly for TLS certificates:Health Check Dependency
Ensure the application only starts after Tailscale is fully connected:- Container has a Tailscale IP
- VPN tunnel is established
- Health endpoint responds
Limitations and Considerations
No Port Mapping
No Port Mapping
You cannot use the
ports: directive in the application container when using network_mode: service:tailscale. Ports must be mapped in the Tailscale container if needed:Shared Network Identity
Shared Network Identity
DNS Configuration
DNS Configuration
DNS settings must be configured in the Tailscale container:
Troubleshooting
Application can't reach localhost
Application can't reach localhost
Symptom: Application logs show connection refused to 127.0.0.1Solution: Verify
network_mode: service:tailscale is set correctly. Check that both containers are running.Tailscale container fails to start
Tailscale container fails to start
Symptom: Container exits immediately or loops restartingSolution:
- Verify
/dev/net/tunexists on the host - Check that
cap_add: - net_adminis present - Ensure
TS_AUTHKEYis valid - Review logs:
docker compose logs tailscale
Application starts before Tailscale ready
Application starts before Tailscale ready
Symptom: Application fails with network errors, then works after restartSolution: Add health check dependency:
Can't access service from Tailnet
Can't access service from Tailnet
Symptom: Service appears in Tailscale admin but connection times outSolution:
- Verify
serve.jsonproxy port matches application port - Check application is listening on 0.0.0.0 or 127.0.0.1
- Review Tailscale logs for serve configuration errors
- Test application locally:
docker exec app-jellyfin curl localhost:8096
Best Practices
Use Health Checks
Always configure health checks for both containers to ensure reliable startup ordering.
Name Consistently
Use consistent naming patterns like
tailscale-{service} and app-{service} for easy management.Document Ports
Comment the application’s port in your compose file even though it’s not explicitly mapped:
Next Steps
Environment Variables
Complete reference for all Tailscale configuration options
Serve vs Funnel
Learn when to expose services privately or publicly
Tailscale Setup
Configure authentication and manage your Tailnet
Deploy Services
Start deploying services with the sidecar pattern