Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

tuna <command>

The default command - wraps your dev server with a Cloudflare tunnel.

Usage

tuna <command> [args...]

Examples

# Node.js / npm scripts
tuna npm run dev
tuna npm start
 
# Vite
tuna vite dev --port 3000
 
# Next.js
tuna next dev
 
# Plain Node
tuna node server.js
 
# Python
tuna python -m http.server 8080
 
# Any command
tuna ./start-dev.sh

What It Does

  1. Reads configuration from package.json
  2. Authenticates with Cloudflare (biometric via Keychain)
  3. Creates tunnel if it doesn't exist
  4. Sets up DNS record pointing to tunnel
  5. Configures access control if specified
  6. Starts cloudflared as a background service
  7. Runs your command with full stdio passthrough
  8. Exits with your command's exit code

Output

$ tuna npm run dev
 
🔐 Touch ID required
 Retrieved credentials for example.com
 Tunnel ready (tuna-alice-api-example-com)
 DNS: alice-api.example.com tunnel
 Service running
 
🌐 https://alice-api.example.com localhost:3000
 
> my-app@1.0.0 dev
> vite
 
  VITE v5.0.0  ready in 234 ms
 
  Local:   http://localhost:3000/
  Tunnel:  https://alice-api.example.com/

Behavior

Stdio Passthrough

All input and output passes through transparently:

  • stdin - Interactive prompts work
  • stdout - Colors and formatting preserved
  • stderr - Error output preserved
  • TTY - Terminal features work (progress bars, etc.)

Signal Handling

SignalBehavior
Ctrl+C (SIGINT)Forwarded to child process
SIGTERMForwarded to child process
SIGHUPForwarded to child process

The tunnel continues running after your command exits. This is intentional - it allows you to restart your dev server without recreating the tunnel.

Exit Codes

Tuna exits with the same code as your command:

Your CommandTuna Exit Code
Exits 00
Exits 11
Killed by signal128 + signal
Ctrl+C130

Configuration Required

Your package.json must have a tuna section:

{
  "tuna": {
    "forward": "my-app.example.com",
    "port": 3000
  }
}

See Configuration for all options.

Idempotent

Running tuna multiple times is safe:

  • Tunnel is reused if it exists
  • DNS record is updated if needed
  • Service is restarted with new config
  • Access policies are synced

Port Matching

Make sure your port config matches your dev server:

{
  "tuna": {
    "port": 3000  // Must match!
  }
}
tuna vite dev --port 3000  # ✅ Matches
tuna vite dev --port 4000  # ❌ Won't work - tunnel points to 3000

See Also