Tunnels

How to Expose Local Services with ASD Tunnels

Published:
Kelvin Wuite
By Kelvin Wuite • 8 min read
Share

ASD gives you multiple ways to expose local services to the internet. From a quick one-liner to a full project configuration, pick the method that matches your workflow. This guide covers all three approaches and when to use each.

Three ways to create tunnels

NeedMethodComplexity
Share something right nowasd exposeSimplest
Daily development workflowasd.yaml configMedium
CI/CD automationTunnel tokensAdvanced

Method 1: Quick expose

The fastest way to share a local port. One command, instant result:

asd expose 3000

Output:

Local:  http://localhost:3000
Caddy:  http://app.localhost
Tunnel: https://app-abc123.cicd.eu1.asd.engineer

Custom names

Give your tunnel a recognizable URL prefix:

asd expose 3000 --name myapp
# Result: https://myapp-abc123.cicd.eu1.asd.engineer

Direct tunnel (skip Caddy)

If you don't need the local Caddy proxy, use --direct to create a tunnel straight to your service:

asd expose 3000 myapp --direct

Managing exposed services

# List all exposed services with their URLs
asd expose list

# Stop by name
asd expose stop myapp

# Stop by port
asd expose stop 3000

Best for: Quick demos, one-off sharing, testing webhooks.

Method 2: Project config (asd.yaml)

For ongoing development, define your services in asd.yaml. This gives you repeatable configuration, multiple services, and local routing through Caddy.

Initialize your project

cd your-project
asd init

This creates asd.yaml and sets up the workspace.

Define your services

version: 1
project:
  name: "my-app"

network:
  services:
    frontend:
      dial: "127.0.0.1:3000"
      host: "app.localhost"
      public: true
      subdomain: "frontend"

    api:
      dial: "127.0.0.1:8080"
      paths: ["/api"]
      public: true
      subdomain: "api"

Each service gets:

  • dial — the local address and port
  • host — an optional local hostname for Caddy routing
  • paths — path-based routing (e.g., /api)
  • public: true — enables the public tunnel
  • subdomain — prefix for the tunnel URL

Apply and start

# Apply config, start Caddy and tunnels
asd net apply --caddy --tunnel

# Open the network TUI to see everything
asd net

The TUI shows all services, their health status, and access URLs:

KeyAction
TabCycle tabs (Services, Projects, Logs)
EnterActions menu for selected service
Ctrl+RRefresh health status
Ctrl+QQuit

Best for: Daily development, team projects, multiple services.

Method 3: Tunnel tokens (CI/CD)

For automated environments like GitHub Actions or GitLab CI, use tunnel tokens instead of interactive authentication.

Ephemeral tokens (quick testing)

No account needed. Get 5-minute credentials instantly:

curl -X POST https://asd.engineer/functions/v1/create-ephemeral-token

Persistent tokens (CI/CD pipelines)

  1. Sign up at asd.host
  2. Go to Account → Tunnel Tokens → Create
  3. Add credentials as CI secrets:
ASD_TUNNEL_TOKEN=your-token-from-dashboard
ASD_TUNNEL_USER=your-user-id

Then in your CI pipeline:

asd expose 3000

Best for: CI/CD pipelines, automated testing, webhook receivers.

Multiple services at once

With asd.yaml, you can expose multiple services simultaneously. Each gets its own tunnel URL:

network:
  services:
    frontend:
      dial: "127.0.0.1:3000"
      host: "app.localhost"
      public: true
      subdomain: "frontend"

    api:
      dial: "127.0.0.1:8080"
      paths: ["/api"]
      public: true
      subdomain: "api"

    docs:
      dial: "127.0.0.1:4000"
      host: "docs.localhost"
      public: true
      subdomain: "docs"
asd net apply --caddy --tunnel

All three services get unique tunnel URLs and local Caddy routes.

Access patterns

Every exposed service is accessible three ways:

PatternURL ExampleUse Case
Local Direct http://localhost:3000 Same machine
Caddy Route http://app.localhost Local network, LAN
Tunnel Remote https://frontend-abc123.cicd.eu1.asd.engineer Anywhere on the internet
Browser → ASD Cloud → SSH Tunnel → Caddy (local) → Your Service
                       (encrypted)        ↑
                                    Routes + Auth

Adding authentication

By default, anyone with the tunnel URL can access your service. Add a password with basic_auth in asd.yaml:

network:
  caddy:
    basic_auth:
      enabled: true
      realm: "My Project"

  services:
    my-app:
      dial: "127.0.0.1:3000"
      public: true

Set credentials in .env:

ASD_BASIC_AUTH_USERNAME=admin
ASD_BASIC_AUTH_PASSWORD=your-secure-password

See the Caddy Basic Auth guide for detailed configuration options.

Troubleshooting

ProblemSolution
Tunnel not connecting Check credentials: asd tunnel auth status
Port already in use asd expose stop 3000 or lsof -i :3000
Service not in TUI asd net refresh to re-scan services
Caddy won't start asd caddy stop, remove .asd/workspace/caddy/, restart

What's next

Kelvin Wuite
Written by

Kelvin Wuite

Kelvin Wuite is the founder of Accelerated Software Development B.V. With over eighteen years of development experience, he has witnessed the same patterns repeat across every software team: endless documentation, manual preparation, environment mismatches, and fragmented collaboration. His drive is to remove these barriers, enabling engineers to work together in unified environments with shorter feedback loops and hands-on collaboration. Since 2015 he has been refining these ideas, leading to ASD — a platform designed to create a faster, more integrated way for development teams to collaborate in an age where AI is thriving.

Related Articles