Run Tailscale on Upsun: Create secure VPN connections for your applications
Tailscale is a VPN service that creates secure, private networks between your devices and applications. By integrating Tailscale with your Upsun applications, you can make your containers part of a “tailnet” - Tailscale’s term for their private networks.
This guide demonstrates how you can leverage Tailscale to connect to external endpoints over a secure network. An example Go application will be added as a placeholder and example for your own application. This setup enables secure communication between your application and other resources in your tailnet.
Prerequisites
Before starting, you’ll need:
- A Tailscale account with an auth key
- Basic familiarity with Upsun configuration
- Understanding of Go applications
Tailscale implementation limitations
Tailscale usually relies on a specific tunnel networking device (tun0
) to route traffic from their machine to their network. Because Upsun run containers directly, a userspace application can’t add a new network interface. To work around this limitation, Tailscale can be started as a HTTP proxy to the tailnet. Our example application will connect to the external endpoints via this exposed HTTP proxy.
Create a basic Go application
Start by creating a simple Go application. Create a go.mod
file:
module example-app
go 1.25
Create a main.go
file:
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Upsun!"))
})
err := http.ListenAndServe(fmt.Sprintf(":%s", os.Getenv("PORT")), nil)
panic(err)
}
Create your initial Upsun configuration in .upsun/config.yaml
:
routes:
https://{default}:
type: upstream
upstream: app:http
applications:
app:
type: golang:1.25
web:
commands:
start: ./hello
locations:
'/':
passthru: true
hooks:
build: go build -o hello
This basic configuration creates a working example Go application on Upsun. You now need to add the Tailscale integration to the project.
Configure multiple processes with Supervisor
Since Upsun’s web.commands.start
is designed to run only one process, you will use Supervisor to start and manage both your application and the Tailscale daemon processes. Update your .upsun/config.yaml
:
routes:
https://{default}:
type: upstream
upstream: app:http
applications:
app:
type: golang:1.25
dependencies:
python3:
supervisor: '*'
web:
commands:
start: supervisord -n -c supervisor.conf
locations:
'/':
passthru: true
hooks:
build: go build -o hello
Create a supervisor.conf
file to manage your processes:
[supervisord]
logfile=/tmp/supervisord.log
nodaemon=true
minfds=1024
pidfile=/tmp/supervisord.pid
[program:hello]
command=/app/hello
process_name=%(program_name)s
autostart=true
autorestart=true
Download and configure Tailscale
The easiest way to run Tailscale is to download and run the amd64
binary directly. Create a script to download Tailscale and cache it between builds. Create scripts/download-tailscale.sh
:
#!/bin/bash
set -e
version=1.86.2
# This uses $PLATFORM_CACHE_DIR to avoid re-downloading the binary on every build
cache="$PLATFORM_CACHE_DIR"/tailscale_"$version"_amd64.tgz
if [ ! -f "$cache" ]; then
curl https://pkgs.tailscale.com/stable/tailscale_"$version"_amd64.tgz -o "$cache"
fi
tar xf "$cache"
mv tailscale_"$version"_amd64 tailscale
Make the script executable:
chmod +x scripts/download-tailscale.sh
Update your build hook in .upsun/config.yaml
to use this script:
applications:
app:
# ... rest of configuration ...
hooks:
build: |
set -e
./scripts/download-tailscale.sh
go build -o hello
Add Tailscale daemon to Supervisor
Update your supervisor.conf
file to include the Tailscale daemon:
[supervisord]
logfile=/tmp/supervisord.log
nodaemon=true
minfds=1024
pidfile=/tmp/supervisord.pid
[program:hello]
command=/app/hello
process_name=%(program_name)s
autostart=true
autorestart=true
[program:tailscale]
command=/app/tailscale/tailscaled --state=mem: --socket=/tmp/tailscaled.sock --tun=userspace-networking --outbound-http-proxy-listen=0.0.0.0:8080 --socks5-server=0.0.0.0:1080
process_name=%(program_name)s
autostart=true
autorestart=true
Key configuration options:
--state=mem:
creates a new Tailscale client on each restart. For persistent clients, use a file on a mount instead--outbound-http-proxy-listen=0.0.0.0:8080
exposes the VPN through an HTTP proxy on port 8080--socks5-server=0.0.0.0:1080
provides SOCKS5 proxy access on port 1080
Connect to your tailnet
After the Tailscale daemon starts, you need to run tailscale up
to join your tailnet. You could add this to your post_start
command:
applications:
app:
# ... rest of configuration ...
web:
commands:
start: supervisord -n -c supervisor.conf
post_start: /app/tailscale/tailscale --socket="$sock" up --auth-key="$TAILSCALE_AUTHKEY"
However, the tailscaled
daemon needs time to start, causing tailscale up
to fail. Instead, create a scripts/tailscale-up.sh
script that waits for the daemon:
#!/bin/bash
set -xe
sock=/tmp/tailscaled.sock
while ! curl --unix-socket "$sock" localhost -w1; do
sleep 0.5
done
/app/tailscale/tailscale --socket="$sock" up --auth-key="$TAILSCALE_AUTHKEY"
Make this script executable:
chmod +x scripts/tailscale-up.sh
And update your Upsun configuration to use this script:
applications:
app:
# ... rest of configuration ...
web:
commands:
start: supervisord -n -c supervisor.conf
post_start: ./scripts/tailscale-up.sh
Set up authentication
Create an environment variable with your Tailscale authentication key:
upsun variable:create env:TAILSCALE_AUTHKEY your-auth-key-here
Your application container is now part of your tailnet!
Using your tailnet connection
Outbound connections
To connect to resources in your tailnet from your application, use the HTTP proxy on port 8080:
# Example with curl
http_proxy=localhost:8080 curl 100.87.175.78
As mentioned earlier, our applications needs to configure their HTTP proxy settings to be able to connect to other machines on the tailnet. Refer to the proxy documentation of your specific runtime or framework.
Inbound connections
Your application becomes immediately accessible from other devices in your tailnet. To expose additional services like databases, use tools like socat
to forward traffic:
# Forward database traffic (example)
socat tcp-listen:3306,bind=0.0.0.0,fork,reuseaddr tcp:database.internal:3306
Next steps
With Tailscale running on Upsun, you can:
- Connect securely to your application from any device in your tailnet
- Access private databases and services through your application
- Create secure communication channels between multiple Upsun applications
- Implement zero-trust networking for your infrastructure
Get started with Upsun and create your secure, scalable application infrastructure today.