mirror of
https://github.com/tiennm99/composes.git
synced 2026-10-11 03:13:16 +00:00
docs(paseo): add setup steps and tighten the README
The pairing screen needs an explicit port, which is the least obvious part of getting connected. Lead with the setup steps and cut the explanation around them down to what a reader has to act on.
This commit is contained in:
1 parent
5edb865597
commit
341469ff43
1 file changed
+52
-42
+52
-42
@@ -1,66 +1,76 @@
|
||||
# paseo
|
||||
|
||||
[Paseo](https://paseo.sh) — a self-hosted daemon and web UI for running coding
|
||||
agents, from the [official image](https://paseo.sh/docs/docker).
|
||||
[Paseo](https://paseo.sh) — self-hosted daemon and web UI for running coding
|
||||
agents. Built from a local `Dockerfile` that adds Claude Code to the
|
||||
[official image](https://paseo.sh/docs/docker), which ships no agent CLIs.
|
||||
|
||||
Built from a local `Dockerfile` rather than the upstream image directly: the
|
||||
official image ships no provider CLIs, so this one layers Claude Code on top
|
||||
via `npm install -g @anthropic-ai/claude-code`. Add other providers
|
||||
(`@openai/codex`, `opencode-ai`) to that same line if you need them.
|
||||
## Setup
|
||||
|
||||
npm installs the same native binary as Anthropic's standalone installer, so
|
||||
there is nothing to gain by switching. The `curl | bash` installer is in fact
|
||||
the wrong choice here — it writes to `$HOME/.local`, and `$HOME` is
|
||||
`/home/paseo`, a volume mount that hides anything baked in at build time.
|
||||
1. Set the three variables from `.env.example` in Coolify or Dokploy.
|
||||
2. Point the domain at port `6767` and deploy.
|
||||
3. Open the domain. At the pairing screen enter the host **with the port**:
|
||||
|
||||
Claude Code cannot auto-update, since `paseo` can't write `/usr/local`; expect
|
||||
a one-time notice at startup. Rebuild the image to pick up a new version.
|
||||
```
|
||||
paseo.example.com:443
|
||||
```
|
||||
|
||||
The image intentionally keeps running as root — its entrypoint chowns the
|
||||
mounted volumes and then drops to the unprivileged `paseo` user with `gosu`.
|
||||
Then the `PASEO_PASSWORD` value.
|
||||
|
||||
Coolify and Dokploy build the image themselves from the compose `build:`
|
||||
stanza; there is nothing to push.
|
||||
The port is required — the UI rejects a bare hostname. You must type the
|
||||
address yourself: the daemon builds its auto-connect hint from the `Host`
|
||||
header, browsers drop the default `:443`, and the UI discards a hint with no
|
||||
port. It then shows its built-in `localhost:6767` placeholder, which in a
|
||||
browser means your own machine.
|
||||
|
||||
Still stuck on `localhost:6767` after entering the address? Clear site data —
|
||||
the old entry is cached in `localStorage`.
|
||||
|
||||
## Environment
|
||||
|
||||
| Variable | Purpose |
|
||||
| --- | --- |
|
||||
| `PASEO_PASSWORD` | Auth for the daemon API and WebSocket |
|
||||
| `PASEO_HOSTNAMES` | Comma-separated DNS names allowed to reach the daemon, e.g. `paseo.example.com,.lan`. IPs and localhost always pass. |
|
||||
| `PASEO_TRUSTED_PROXIES` | Proxy addresses whose `X-Forwarded-*` headers the daemon believes. Required behind a TLS-terminating proxy — see below. |
|
||||
| `PASEO_PASSWORD` | Web UI and API login. Generate with `openssl rand -base64 24`. |
|
||||
| `PASEO_HOSTNAMES` | Domains allowed to reach the daemon, comma-separated. Your domain must be listed. |
|
||||
| `PASEO_TRUSTED_PROXIES` | Set to `uniquelocal`, or the UI loads but never connects. |
|
||||
|
||||
Generate a password with `openssl rand -base64 24`. The proxied domain must
|
||||
appear in `PASEO_HOSTNAMES` or requests are rejected.
|
||||
`PASEO_TRUSTED_PROXIES` matches the *source IP* of the proxy, so hostnames are
|
||||
rejected. By default the daemon believes `X-Forwarded-Proto` only from
|
||||
loopback, but Coolify's Traefik reaches it from the Docker bridge network. It
|
||||
therefore reads the request as plain HTTP, tells the UI to use `ws://` on an
|
||||
`https://` page, and the browser blocks that as mixed content. `uniquelocal`
|
||||
covers the private ranges Docker uses; an exact CIDR works too, but Coolify
|
||||
assigns a fresh subnet per project.
|
||||
|
||||
## Networking
|
||||
|
||||
Listens on `6767`. No ports are published — point the domain at that port in
|
||||
Coolify or Dokploy. See the [root README](../README.md) for why. `localhost:6767`
|
||||
on the host will refuse connections; reach the daemon through its domain.
|
||||
|
||||
`PASEO_TRUSTED_PROXIES` must be set, or the web UI loads but never connects.
|
||||
The daemon trusts `X-Forwarded-Proto` from loopback only by default. Coolify's
|
||||
Traefik reaches it from the Docker bridge network instead, so the daemon
|
||||
concludes the request was plain HTTP and hands the UI `useTls: false`. The UI
|
||||
then builds a `ws://` URL from an `https://` page, the browser blocks it as
|
||||
mixed content, and the UI falls back to its built-in `localhost:6767` default —
|
||||
which in a browser means the viewer's own machine, not the server.
|
||||
|
||||
`uniquelocal` covers the private ranges Docker uses. A specific CIDR works too,
|
||||
but Coolify assigns a fresh subnet per project, so it will not survive a move.
|
||||
Listens on `6767`, published nowhere — the platform maps the domain to it, so
|
||||
`localhost:6767` on the host refuses connections. See the
|
||||
[root README](../README.md) for why.
|
||||
|
||||
## Storage
|
||||
|
||||
Two named volumes:
|
||||
|
||||
| Volume | Mount | Holds |
|
||||
| --- | --- | --- |
|
||||
| `paseo-home` | `/home/paseo` | Daemon state, agent configs, credentials (`.codex`, `.claude`) |
|
||||
| `paseo-home` | `/home/paseo` | Daemon state, agent configs, credentials (`.claude`, `.codex`) |
|
||||
| `paseo-workspace` | `/workspace` | Code the agents work on |
|
||||
|
||||
Claude Code's own config lives under `/home/paseo/.claude`, so it persists in
|
||||
`paseo-home` — credentials survive a redeploy.
|
||||
Claude Code's config lives in `/home/paseo/.claude`, so logins survive a
|
||||
redeploy.
|
||||
|
||||
The daemon runs as uid/gid `1000:1000`; anything bind-mounted in its place must
|
||||
be writable by that user.
|
||||
## Image
|
||||
|
||||
Add other providers to the `npm install` line in the `Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
RUN npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai
|
||||
```
|
||||
|
||||
Notes for anyone tempted to change it:
|
||||
|
||||
- npm installs the same native binary as Anthropic's standalone installer.
|
||||
Don't swap in `curl | bash` — it writes to `$HOME/.local`, and `$HOME` is
|
||||
`/home/paseo`, a volume mount that hides anything baked in at build time.
|
||||
- Claude Code can't auto-update (`paseo` can't write `/usr/local`), so it shows
|
||||
a notice at startup. Rebuild to update.
|
||||
- The image stays root on purpose: the entrypoint chowns the volumes, then
|
||||
drops to the `paseo` user (uid 1000) with `gosu`.
|
||||
Reference in new issue
Block a user