Self-hosted Paseo web UI and paseo-relay, so a Paseo instance (daemon) can be reached from a browser through your own relay.
The recommended build is the all-in-one image (paseo-all/): web UI +
relay in one container, published on one port (8080), with the relay on a
subpath:
http://host:8080/ web UI (nginx static SPA)
ws://host:8080/ws relay WebSocket (nginx -> relay on 127.0.0.1:4000)
http://host:8080/health relay liveness
http://host:8080/ready relay readiness
The Paseo client protocol always connects to the relay at the /ws path, so
subpath routing is transparent to both the daemon and the browser. Using one
origin also avoids mixed-content (http page + ws) and CORS issues.
Browser ──► http(s)://host:8080/ (web UI)
│
│ ws://host:8080/ws (pairing offer pasted in browser)
▼
nginx (same container)
│
▼
paseo-relay (127.0.0.1:4000)
▲
│ ws://host:8080/ws (PASEO_RELAY_ENDPOINT)
Paseo daemon (your instance)
cd paseo-tools
./build.sh # resolves latest paseo tag + relay commit, builds relzhong/paseo:latest
nerdctl run -d --name paseo -p 8080:8080 relzhong/paseo:latest
# or with compose:
cp .env.example .env # set RELAY_PUBLIC_ENDPOINT to your LAN IP
nerdctl compose up -d --build
The Dockerfiles clone from GitHub at build time, so every build pulls the
newest upstream code with no manual bumping. build.sh resolves the exact
latest release tag (web UI) and commit (relay) before invoking the build and
passes them as build args, which makes BuildKit's layer cache effective:
rebuilding while nothing changed is near-instant, and a new upstream version
only redoes the changed stage (deps are cached with BuildKit cache mounts).
Pin a version with --build-arg PASEO_TAG=v0.3.1 / --build-arg RELAY_REF=<sha>.
Note: the first build is heavy (npm workspaces + Expo export + Elixir release) and can take 10-30 minutes depending on network; subsequent builds are fast.
The builds default to mirrors that are fast in China; override per environment:
| Build arg | Default | Purpose |
|---|---|---|
APT_MIRROR | mirrors.tuna.tsinghua.edu.cn | apt sources (set deb.debian.org elsewhere) |
NPM_REGISTRY | https://registry.npmmirror.com | npm registry (set https://registry.npmjs.org elsewhere) |
HTTPS_PROXY | unset | optional proxy for GitHub clones / hex.pm (build.sh forwards your env) |
The web UI build runs npm ci --workspace=@getpaseo/app, installing only the
app and the workspaces it depends on (@getpaseo/client, @getpaseo/highlight,
@getpaseo/protocol, @getpaseo/expo-two-way-audio). The desktop, server,
CLI and website workspaces — including the heavy electron binary download —
are intentionally skipped.
On the machine running the daemon (paseo CLI / ghcr.io/getpaseo/paseo
image), first enable the relay, then set the endpoint:
# enable: fresh install, or restart an existing daemon with the relay on
paseo onboard --relay
paseo daemon restart --relay
# set the endpoint (env vars, taken on daemon start)
export PASEO_RELAY_ENDPOINT=paseo.your-domain.com # daemon -> relay connection
export PASEO_RELAY_PUBLIC_ENDPOINT=paseo.your-domain.com # advertised to browsers
export PASEO_RELAY_USE_TLS=true
export PASEO_RELAY_PUBLIC_USE_TLS=true
paseo daemon restart
<host>:<port> is the relay address from the daemon's perspective. With a
public domain + TLS reverse proxy in front of the relay, use wss:// by
setting USE_TLS=true; on a plain LAN use ws:// (USE_TLS=false). The
protocol appends the /ws path automatically.
Equivalent persisted config at ~/.paseo/config.json:
{ "daemon": { "relay": {
"enabled": true,
"endpoint": "paseo.your-domain.com",
"useTls": true,
"publicEndpoint": "paseo.your-domain.com",
"publicUseTls": true
} } }
Note: paseo daemon restart --relay only toggles enabled; the endpoint
always comes from env vars or the persisted config.
The paseo CLI connects through the relay too — just point it at the daemon's pairing offer URL (no port forwarding needed):
npm install -g @getpaseo/cli
export PASEO_HOST="https://app.paseo.sh/#offer=<paste the offer from 'paseo onboard --relay'>"
paseo ls
paseo run "implement the auth flow"
The offer embeds the relay endpoint, TLS, serverId and the daemon's E2EE
public key, so the CLI reaches the daemon via wss://paseo.your-domain.com/ws.
Alternative (no relay, direct TCP): the daemon must listen on a reachable
interface (PASEO_LISTEN=0.0.0.0:6767), then on the client:
export PASEO_HOST=<ip>:6767 and export PASEO_PASSWORD=... if auth is set.
http://paseo.iriscv.com:8080 (plain http is fine —
the relay itself is reached as wss://).paseo onboard --relay (or the QR shown
at first start).PASEO_RELAY_PUBLIC_ENDPOINT/USE_TLS, so the browser connects through
your relay to the instance.paseo-relay/ and paseo-webui/ build the two components separately, in case
you want to scale the relay independently:
nerdctl build -t relzhong/paseo-relay:latest ./paseo-relay # port 4000
nerdctl build -t relzhong/paseo-webui:latest ./paseo-webui # port 80
The relay is stateless (route ownership and metrics live in BEAM memory; clients simply reconnect), so the all-in-one image needs no volume. The state that matters is:
-v paseo-home:/home/paseo -v "$PWD:/workspace".Optional: persist BEAM crash dumps / log files with
-v paseo-relay-data:/opt/paseo-relay/log.
http:// web UI + ws://
relay works on your LAN. If you expose the web UI over HTTPS you must also
put a TLS proxy in front of it so browsers can use wss:// for /ws.ELIXIR_IMAGE
in the Dockerfiles to the tag used by the upstream paseo-relay Dockerfile.Content type
Image
Digest
sha256:2d315bdec…
Size
47.9 MB
Last updated
2 days ago
docker pull relzhong/paseo