Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Practice] Embedding Paseo (self-hosted agent orchestration console) as a Coder app — field-tested setup #28933

Description

@TommyLike

Context / why

For teams running Coder as their dev-environment layer, a natural next step is a self-hosted agent orchestration console — one place to spawn, monitor and hand off Claude Code / Codex / OpenCode agents across machines. Paseo (self-hosted daemon + web UI for exactly that) is a good fit, but the obvious "add it as an external app" approach opens a second, Coder-independent network exposure. This issue documents a zero-code, tunnel-managed embedding (Paseo daemon lives inside the workspace; the Coder tunnel is the only ingress) that we have validated end-to-end, and asks whether upstream would consider first-class support (see "Ask").

What we did (validated on coder v2.36.3 + Paseo 0.7.2)

  1. Paseo runs inside the Coder workspace container, listening on 127.0.0.1:6767 only. No host port mapping (docker port = empty).
  2. Exposed via a non-external coder_app with subdomain = true — Coder's WireGuard tailnet is the single ingress; requests pass Coder auth (session + share = "owner") on every hop.
  3. Key blocker found: Paseo's web UI is an Expo SPA with absolute asset paths (/_expo/static/...) and does not support a base path — so path-based apps render a blank page. Subdomain mode is mandatory.
  4. Second blocker found: Paseo's daemon validates the Host header against daemon.hostnames. When Coder forwards a subdomain request the Host is <slug>--<ws>--<owner>.<wildcard>:<port>, which is rejected by default ({"error":"Invalid Host header"}). Fix: pre-write the daemon config in the agent startup script (see template below).
  5. Claude Code (with a custom settings.json, e.g. a DeepSeek/Anthropic-compatible endpoint) is pre-installed in the workspace image; the daemon spawns it against the workspace's own project directory (/home/coder).

Benefits

  • Zero extra network exposure: the only public endpoint stays Coder (auth + WireGuard tunnel).
  • Per-workspace isolation out of the box: each user's daemon, agents and API keys live in their own workspace and die with it (coder stop/delete = everything released) — matches Coder's lifecycle semantics.
  • No upstream modification of Paseo: everything is config (hostnames/cors) + the standard coder_app resource.

How to configure (minimal template)

resource "coder_agent" "main" {
  startup_script = <<-EOF
    export PASEO_HOME=/home/coder/.paseo
    mkdir -p /home/coder/.paseo
    cat > /home/coder/.paseo/config.json <<'PASEO_CFG'
{
  "version": 1,
  "daemon": {
    "listen": "127.0.0.1:6767",
    "hostnames": ["paseo--<workspace>--<owner>.<wildcard>", "localhost", "127.0.0.1"],
    "cors": { "allowedOrigins": ["*"] },
    "relay": { "enabled": false }
  },
  "app": { "baseUrl": "http://localhost:6767" }
}
PASEO_CFG
    paseo daemon start --web-ui --listen 127.0.0.1:6767 > /home/coder/.paseo/daemon.log 2>&1 &
    sleep 3
    paseo project ls 2>/dev/null | grep -q "/home/coder" || paseo project create /home/coder > /dev/null 2>&1
  EOF
}

resource "coder_app" "paseo" {
  agent_id     = coder_agent.main.id
  slug         = "paseo"
  display_name = "Paseo"
  url          = "http://localhost:6767"
  subdomain    = true          # mandatory: SPA assets are absolute-path
  share        = "owner"
  healthcheck { url = "http://localhost:6767/api/health"; interval = 5; threshold = 10 }
}

Server side: CODER_WILDCARD_ACCESS_URL="*.example.com:7080" (no scheme; include the port or generated links miss it).

Caveats

  • hostnames is hard-coded per workspace slug today; a wildcard/parametrized allow-list would remove the coupling (see Ask).
  • Logout invalidates access immediately — every request is re-checked by Coder auth (verified).
  • One daemon per workspace (resource cost is small, but it is per-user).
  • Paseo's permission model is daemon-wide (no user-level scoping yet) — the per-workspace-instance pattern is what makes it multi-tenant safe.

Ask (upstream)

  1. Would Coder consider a documented "embedded service app" pattern (non-external apps already proxy arbitrary TCP/HTTP to the agent; a cookbook entry for long-running daemons like Paseo would help)?
  2. For Paseo: a wildcard/parameterized hostnames option would remove the per-workspace config coupling when embedded behind a proxy.

Environment: coder v2.36.3 (Docker Compose + PostgreSQL), Paseo 0.7.2, workspace image based on codercom/enterprise-base with @getpaseo/cli + @anthropic-ai/claude-code.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions