mirror of
https://github.com/tiennm99/composes.git
synced 2026-10-13 10:15:39 +00:00
Drop the history and roadmap asides: which services predate the collection's
conventions, the unwired Open Web UI plan, the generic clone-and-troubleshoot
boilerplate. Services that publish ports or set `restart:` now simply say so.
couchbase, openvpn-as and traffmonetizer had two-line READMEs; give them the
ports, variables and storage the root README promises. traffmonetizer reads
${TOKEN} and had no .env.example, so add one.
119 lines
5.4 KiB
Markdown
119 lines
5.4 KiB
Markdown
# paseo
|
|
|
|
[Paseo](https://paseo.sh) — self-hosted daemon and web UI for running coding
|
|
agents. Built from a local `Dockerfile` that adds six agent CLIs, `gh`, Go,
|
|
Python, and shell tooling to the
|
|
[official image](https://paseo.sh/docs/docker), which ships none of it.
|
|
|
|
## Setup
|
|
|
|
1. Set the 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**,
|
|
then the `PASEO_PASSWORD` value:
|
|
|
|
```
|
|
paseo.example.com:443
|
|
```
|
|
|
|
4. In a terminal inside Paseo, log in once per tool you use — see
|
|
[Agents](#agents) — plus `gh auth login`.
|
|
|
|
The port must be typed by hand. The UI rejects a bare hostname, and the
|
|
auto-connect hint does not help: the daemon builds it from the `Host` header,
|
|
browsers drop the default `:443`, and the UI discards a hint with no port. What
|
|
you see instead is its `localhost:6767` placeholder, which in a browser means
|
|
your own machine.
|
|
|
|
If it stays on `localhost:6767` after you enter the address, clear site data —
|
|
the old entry is cached in `localStorage`.
|
|
|
|
## Environment
|
|
|
|
| Variable | Purpose |
|
|
| --- | --- |
|
|
| `PASEO_PASSWORD` | Web UI and API login, and the `paseo` user's `sudo` password. 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. |
|
|
| `PASEO_LABEL` | Container hostname, shown as the host label in the UI. Without it the label is a random container ID. |
|
|
| `GIT_NAME` / `GIT_EMAIL` | Git author and committer identity for agents and terminals. |
|
|
| `TZ` | Timezone for logs and agent shells. |
|
|
| `SHELL` | Shell for Paseo's terminals. Paseo reads `$SHELL` and falls back to `/bin/sh`, ignoring the login shell, so `chsh` has no effect. |
|
|
|
|
`PASEO_TRUSTED_PROXIES` matches the proxy's *source IP*, so hostnames are
|
|
rejected. The daemon trusts `X-Forwarded-Proto` from loopback only, but
|
|
Coolify's Traefik reaches it from the Docker bridge network — so it 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`, 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.
|
|
|
|
## Agents
|
|
|
|
| Agent | Command | Package | Log in with |
|
|
| --- | --- | --- | --- |
|
|
| Claude Code | `claude` | `@anthropic-ai/claude-code` | `claude` |
|
|
| Codex | `codex` | `@openai/codex` | `codex login` |
|
|
| opencode | `opencode` | `opencode-ai` | `opencode auth login` |
|
|
| Copilot CLI | `copilot` | `@github/copilot` | `/login` inside `copilot` |
|
|
| Pi | `pi` | `@earendil-works/pi-coding-agent` | `pi` |
|
|
| Oh My Pi | `omp` | `@oh-my-pi/pi-coding-agent` | `omp` |
|
|
|
|
All six come from npm, so another provider is one more entry on the `npm
|
|
install` line. Pi and Oh My Pi are separate projects sharing an ancestor; their
|
|
commands do not collide.
|
|
|
|
## Storage
|
|
|
|
| Volume | Mount | Holds |
|
|
| --- | --- | --- |
|
|
| `paseo-home` | `/home/paseo` | Daemon state, agent configs, credentials (`.claude`, `.codex`, `.config/*`) |
|
|
| `paseo-workspace` | `/workspace` | Code the agents work on |
|
|
|
|
Every agent CLI and `gh` keep their config under `/home/paseo`, so all logins
|
|
survive a redeploy — the base image points `CLAUDE_CONFIG_DIR`, `CODEX_HOME`
|
|
and the `XDG_*` variables into it. Dotfiles live there too, so a `.zshrc` or
|
|
oh-my-zsh install persists. Anything written outside `$HOME` (`chsh`,
|
|
`apt install`) is lost on rebuild.
|
|
|
|
## Image
|
|
|
|
| Tool | Source | Why not apt |
|
|
| --- | --- | --- |
|
|
| Agent CLIs (see [above](#agents)) | npm | — |
|
|
| Bun | Official `bun.sh` install script | Debian does not package it |
|
|
| `gh` | GitHub's signed apt repo | Debian does not package it |
|
|
| Go (`GO_VERSION`) | Official go.dev tarball | Debian 12 ships 1.19 |
|
|
| Python (`PYTHON_VERSION`) | `uv python install` | Debian 12 ships 3.11 |
|
|
| `less nano jq unzip zip lsof psmisc ugrep bfs zsh sudo` | apt | — |
|
|
|
|
Bump a language with a build arg, e.g. `--build-arg GO_VERSION=1.27.1`. `uv`
|
|
itself is installed too.
|
|
|
|
Constraints worth knowing before changing the `Dockerfile`:
|
|
|
|
- `$HOME` is `/home/paseo`, a volume that masks anything the build writes
|
|
there. Hence `/opt/python` and `/usr/local/go` rather than the defaults, and
|
|
hence npm rather than each vendor's `curl | bash` installer, which writes to
|
|
`$HOME/.local`. npm delivers the same native binaries regardless — every one
|
|
of these packages is a thin launcher with the real binary as a per-platform
|
|
optional dependency.
|
|
- Bun exists for `omp` alone, whose bin opens with `#!/usr/bin/env bun`.
|
|
Node runs the other five.
|
|
- The agent CLIs cannot auto-update, since `paseo` cannot write `/usr/local`.
|
|
Claude Code shows a notice at startup. Rebuild to update.
|
|
- The image stays root: the entrypoint chowns the volumes, then drops to the
|
|
`paseo` user (uid 1000) with `gosu`.
|
|
- `entrypoint.sh` sets the `paseo` password from `PASEO_PASSWORD` on every
|
|
start, while still root. `/etc/shadow` is not on a volume, so it reverts on
|
|
each recreate. An empty `PASEO_PASSWORD` leaves the account locked and `sudo`
|
|
unusable.
|
|
- `sudo` resets `PATH` to its `secure_path`, which excludes
|
|
`/usr/local/go/bin`. Use `sudo env PATH="$PATH" go ...` or the full path.
|