Documentation / Project Config

Project Config (stunl.yaml)

Define your services, routing, auth, and environment presets in one file. Check it into git. Run stunl up.

Overview

stunl.yaml is a project-level configuration file that declares your tunnel topology — which services to expose, how to route traffic, and who can access them.

Instead of passing flags every time, you define it once and every teammate gets the same setup. Think of it as your compose file for tunnels.

Before: CLI flags every time
$ stunl -id myapp \
  -ports 'web:3000,api:8080,db:5432:tcp' \
  -routing-strategy path \
  -password secret \
  -oauth github \
  -oauth-github-org mycompany
After: one command
$ stunl up

Loading stunl.yaml...
Starting 3 services...
  web     → localhost:3000 (http)
  api     → localhost:8080 (http)
  db      → localhost:5432 (tcp)

Quick Start

1

Generate a config

stunl auto-detects your running services and generates a config file.

$ stunl init

Detecting running services...
Created stunl.yaml

Detected 3 services:
  web      → port 3000 (http)
  api      → port 8080 (http)
  postgres → port 5432 (tcp)
2

Edit the config (optional)

Tweak names, add auth, define environments.

3

Start your tunnels

$ stunl up
4

Commit to git

Now everyone on your team runs stunl up and gets the exact same tunnel topology.

Full Schema

Here's a complete stunl.yaml showing all available options.

stunl.yaml
# Schema version (required, must be "1")
version: "1"

# Tunnel subdomain (optional, auto-generated if omitted)
name: my-project

# Custom domain (optional, requires Pro+ tier)
# domain: mycompany.com

# Routing strategy for multi-service configs
# Options: path, subdomain, header, mixed, round_robin, least_connections
routing: path

# Services to expose (at least one required)
services:
  frontend:
    port: 3000
    # protocol: http        (default)
    # host: localhost       (default)

  api:
    port: 8080
    path: /api             # URL path prefix for routing

  websocket:
    port: 8081
    protocol: websocket
    path: /ws

  postgres:
    port: 5432
    protocol: tcp
    public_port: 15432    # Preferred public port (10001-19999)

# Access control (optional)
auth:
  password: "secret123"
  # OR use OAuth (mutually exclusive with password):
  # oauth:
  #   provider: github
  #   allowed_domains: [mycompany.com]
  #   allowed_emails: [alice@example.com]
  #   github_orgs: [my-org]
  #   github_teams: [my-org/engineering]

# Environment presets (optional)
environments:
  dev:
    description: "Full development stack"
    # All services included by default

  demo:
    description: "Frontend + API for demos"
    services: [frontend, api]
    name: my-project-demo
    auth:
      oauth:
        provider: github
        github_orgs: [my-org]

  webhook:
    description: "API only for webhook testing"
    services: [api]
    name: my-project-webhooks

Services

Each service maps a name to a local port. At least one service is required.

Field Required Default Description
port Yes Local port number (1–65535)
protocol No http http, tcp, udp, or websocket
host No localhost Local hostname (single-service configs only)
path No URL path prefix for routing (e.g., /api)
public_port No Preferred public port for TCP/UDP (10001–19999)

Single-service shortcut

When you define only one service, stunl skips multi-port overhead and creates a simple tunnel — exactly like running stunl -port 3000.

Protocols

HTTP (default) + WebSocket
services:
  web:
    port: 3000                # protocol defaults to http
  realtime:
    port: 8081
    protocol: websocket      # dedicated WebSocket service
TCP (databases, SSH, game servers)
services:
  postgres:
    port: 5432
    protocol: tcp
    public_port: 15432       # reserved port for consistent access
  redis:
    port: 6379
    protocol: tcp

# Then connect from anywhere:
# psql -h my-project.stunl.io -p 15432 -U postgres
UDP (game servers)
services:
  minecraft:
    port: 25565
    protocol: udp
    public_port: 10042       # friends always connect to this port

Authentication

Restrict who can access your tunnel. Password auth and OAuth are mutually exclusive.

Password protection
auth:
  password: "my-secret-password"
OAuth with GitHub
auth:
  oauth:
    provider: github           # github, google, or microsoft
    github_orgs: [my-org]     # restrict to org members
    github_teams: [my-org/eng] # or specific teams
OAuth with Google (domain restriction)
auth:
  oauth:
    provider: google
    allowed_domains: [mycompany.com]

Environment Presets

Define named presets that filter services and override settings. Run different slices of your stack for different purposes.

Environments in stunl.yaml
environments:
  dev:
    description: "Full stack for development"
    # All services included when services list is omitted

  demo:
    description: "Frontend + API for client demos"
    services: [frontend, api]      # only these services
    name: my-project-demo           # different subdomain
    auth:
      oauth:
        provider: github
        github_orgs: [my-org]

  webhook:
    description: "API for webhook testing"
    services: [api]               # just the API
    name: my-project-webhooks
Using environments
# List available environments
$ stunl up --list-envs
  demo                 Frontend + API for client demos
  dev                  Full stack for development
  webhook              API for webhook testing

# Start a specific environment
$ stunl up --env demo
Starting 2 services (env: demo)...
  frontend → localhost:3000 (http)
  api      → localhost:8080 (http)
Override Field Behavior
services Filter to only these services. Omit to include all.
name Override the tunnel subdomain.
domain Override the custom domain.
routing Override the routing strategy.
auth Override auth. Different presets can have different access controls.

Routing Strategies

Controls how traffic is distributed across your HTTP/WebSocket services.

Strategy Description
path Route by URL path prefix (default). /api → API service, / → frontend.
subdomain Route by subdomain. api.myapp.stunl.io → API service.
header Route by custom HTTP header value.
mixed Combination of path, subdomain, and header routing.
round_robin Distribute requests evenly across services.
least_connections Route to the service with fewest active connections.

CLI Reference

stunl up
$ stunl up                    # auto-discover stunl.yaml, start all services
$ stunl up --env demo           # start with environment preset
$ stunl up --file ./other.yaml  # use a specific config file
$ stunl up --headless           # run without the TUI
$ stunl up --list-envs          # list available environments
stunl init
$ stunl init                   # detect services, write stunl.yaml
$ stunl init --output .stunl.yaml  # custom output path
$ stunl init --force           # overwrite existing file

Config Discovery

stunl up walks up from the current directory looking for a config file, similar to how git finds .git. The following filenames are recognized:

  • stunl.yaml
  • .stunl.yaml (hidden file)
  • stunl.yml
  • .stunl.yml

Config hierarchy

stunl.yaml defines tunnel topology (services, routing, auth) and goes into git. Your personal settings (API key, server address, TLS) live in ~/.stunl/config.yaml and stay local. When you run stunl up, both are merged — connection settings from your personal config, tunnel topology from the project config.

Real-World Examples

Next.js + Go API + PostgreSQL
version: "1"
name: acme-app
routing: path

services:
  web:
    port: 3000
  api:
    port: 8080
    path: /api
  db:
    port: 5432
    protocol: tcp

auth:
  password: "dev-secret"
Streamlit + FastAPI
version: "1"
name: ml-dashboard

services:
  dashboard:
    port: 8501                 # Streamlit default port
  api:
    port: 8000                 # FastAPI default port
    path: /api

environments:
  share:
    description: "Dashboard only for stakeholders"
    services: [dashboard]
    auth:
      oauth:
        provider: google
        allowed_domains: [mycompany.com]
Single service (simple mode)
version: "1"
name: my-api

services:
  api:
    port: 8080

# Even for single services, stunl.yaml is useful:
# - Consistent tunnel name across the team
# - Auth settings checked into version control
# - No need to remember CLI flags

Validation Rules

stunl validates your config on load and gives clear errors for any problems.

  • version must be "1"
  • At least one service must be defined
  • Ports must be 1–65535, no duplicates across services
  • Protocols must be http, tcp, udp, or websocket
  • public_port only on TCP/UDP services (range 10001–19999)
  • Path prefixes must start with /
  • Password and OAuth are mutually exclusive
  • OAuth provider must be github, google, or microsoft
  • Environment service references must exist in the top-level services
  • Service names cannot contain : @ or ,

Next Steps