From 3cd1f0ec9f92f240ffe2dc902468f7586f21db4b Mon Sep 17 00:00:00 2001 From: weakish Date: Sat, 2 May 2026 15:31:30 +0000 Subject: [PATCH 1/2] doc how to run the image via podman --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c909c70..9f7b66f 100644 --- a/README.md +++ b/README.md @@ -70,19 +70,20 @@ You pull it. You run it. You open your browser. You build. | 5 | [Provider Support](#-provider-support) | | 6 | [Docker Compose - Quick](#-docker-compose---quick) | | 7 | [Docker Compose - Full](#-docker-compose---full) | -| 8 | [Environment Variables](#-environment-variables) | -| 9 | [What's Inside](#-whats-inside) | -| 10 | [Bundled Services](#-bundled-services) | -| 11 | [Architecture](#-architecture) | -| 12 | [CLI Usage](#-cli-usage) | -| 13 | [Data and Persistence](#-data-and-persistence) | -| 14 | [Permissions](#-permissions) | -| 15 | [Upgrading](#-upgrading) | -| 16 | [Troubleshooting](#-troubleshooting) | -| 17 | [Building Locally](#-building-locally) | -| 18 | [Contributing](#-contributing) | -| 19 | [Support](#-support) | -| 20 | [License](#-license) | +| 8 | [Podman](#-podman) | +| 9 | [Environment Variables](#-environment-variables) | +| 10 | [What's Inside](#-whats-inside) | +| 11 | [Bundled Services](#-bundled-services) | +| 12 | [Architecture](#-architecture) | +| 13 | [CLI Usage](#-cli-usage) | +| 14 | [Data and Persistence](#-data-and-persistence) | +| 15 | [Permissions](#-permissions) | +| 16 | [Upgrading](#-upgrading) | +| 17 | [Troubleshooting](#-troubleshooting) | +| 18 | [Building Locally](#-building-locally) | +| 19 | [Contributing](#-contributing) | +| 20 | [Support](#-support) | +| 21 | [License](#-license) | --- @@ -404,6 +405,35 @@ services: --- +## 🐳 Podman + +Prefer Podman? Same image, one command. No compose file needed. + +```bash +mkdir -p data/opencode local-cache/opencode workspace + +podman start holycode 2>/dev/null || podman run -d \ + --name holycode \ + --restart unless-stopped \ + --shm-size=2g \ + -p 4096:4096 \ + -v ./data/opencode:/home/opencode \ + -v ./local-cache/opencode:/home/opencode/.cache/opencode \ + -v ./workspace:/workspace \ + -e PUID=$(id -u) \ + -e PGID=$(id -g) \ + -e ANTHROPIC_API_KEY=your-key-here \ + docker.io/coderluii/holycode:latest +``` + +Open http://localhost:4096. You're in. + +

+ back to top +

+ +--- + ## 📦 What's Inside
From 28ca5c862cbb7c0df6c2301516d0f942393f662e Mon Sep 17 00:00:00 2001 From: CoderLuii Date: Wed, 27 May 2026 20:36:24 +0000 Subject: [PATCH 2/2] docs(readme): add Podman guide --- README.md | 99 ++++++++++++++++-------- docs/podman.md | 200 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+), 30 deletions(-) create mode 100644 docs/podman.md diff --git a/README.md b/README.md index 9f7b66f..75254e8 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,26 @@ services: # - ENABLE_OH_MY_OPENAGENT=true ``` +For the optional CLIProxyAPI sidecar, use the shipped `docker-compose.full.yaml` profile instead of the quick-start compose: + +```bash +docker compose -f docker-compose.full.yaml --profile cliproxyapi up -d +``` + +HolyCode reaches that sidecar at `http://cliproxyapi:8317/v1` inside the Compose network. The sidecar is disabled by default and does not replace Claude Auth. + +

+ back to top +

+ +--- + +## 🐳 Podman + +Prefer Podman? HolyCode uses the same container image there too. The Podman guide covers the minimal `podman run` setup, env-file usage, SELinux labels, rootless permissions, and update/recreate behavior. + +**[Read the Podman guide](docs/podman.md)** +

back to top

@@ -377,8 +397,14 @@ services: | `PAPERCLIP_PORT` | `3100` | Override the container port used by Paperclip | | `PAPERCLIP_INSTANCE_ID` | `default` | Local Paperclip instance name for isolated state | | `PAPERCLIP_DEPLOYMENT_MODE` | `authenticated` | Docker-safe Paperclip startup mode; HolyCode defaults this away from `local_trusted` | +| `PAPERCLIP_ALLOWED_HOSTNAMES` | (none) | Comma-separated Paperclip remote hostnames/IPs to allow; use hostname/IP only, no scheme or port | | `ENABLE_HERMES` | (none) | Set to `true` to start Hermes as a bundled meta-agent API | | `HERMES_PORT` | `8642` | Override the container port used by Hermes | +| `CLIPROXYAPI_ENABLED` | (none) | Set to `true` to add the optional OpenCode `cliproxyapi` provider | +| `CLIPROXYAPI_BASE_URL` | `http://cliproxyapi:8317/v1` | CLIProxyAPI OpenAI-compatible base URL from the HolyCode container | +| `CLIPROXYAPI_API_KEY` | (none) | Optional API key for CLIProxyAPI, stored only as an OpenCode env reference when set | +| `CLIPROXYAPI_MODEL` | (none) | Optional primary model key exposed as `cliproxyapi/` | +| `CLIPROXYAPI_SMALL_MODEL` | (none) | Optional smaller/faster model key exposed as `cliproxyapi/` | | `HOLYCODE_PLUGIN_UPDATE` | `manual` | Plugin update mode: `manual` (install if missing) or `auto` (install and update on boot) | > Plugin toggles (`ENABLE_CLAUDE_AUTH`, `ENABLE_OH_MY_OPENAGENT`) take effect on container restart. Set the env var and run `docker compose down && up -d`. @@ -393,40 +419,17 @@ services: > HolyCode forces `PAPERCLIP_DEPLOYMENT_MODE=authenticated` by default because Paperclip's upstream `local_trusted` mode only allows loopback binding. In Docker, that would block port publishing on `0.0.0.0`. +> `PAPERCLIP_ALLOWED_HOSTNAMES` lets Paperclip accept listed LAN/private hostnames or IPs. Use comma-separated hostname/IP values only, without `http://`, `https://`, or ports. Restart the container after changing it. The hostname guard and Paperclip authentication stay enabled. + > `ENABLE_HERMES=true` starts Hermes on port `8642` inside the container. Hermes persists under `~/.hermes`, uses the already-installed `opencode` binary, and can expose an OpenAI-compatible API while delegating code work back into HolyCode. > Hermes is an API service, not a landing page. A `404` at `http://localhost:8642/` is expected. The important signal is that the port is listening and the process stays healthy. -> `GIT_USER_NAME` and `GIT_USER_EMAIL` are only applied on first boot. To re-apply, delete the sentinel file and restart: `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` then `docker compose restart`. - -

- back to top -

+> `CLIPROXYAPI_ENABLED=true` adds a separate OpenCode provider named `cliproxyapi`. It does not change `ENABLE_CLAUDE_AUTH`, does not touch `/home/opencode/.claude`, and does not set global `ANTHROPIC_*` proxy variables. When using the full-compose sidecar, keep `CLIPROXYAPI_BASE_URL=http://cliproxyapi:8317/v1`. ---- - -## 🐳 Podman +> CLIProxyAPI sidecar state lives under `./local-cache/cliproxyapi-config`, `./local-cache/cliproxyapi-auth`, and `./local-cache/cliproxyapi-logs`. The API port `8317` is not published to the host by default; uncomment loopback-only host publishing in `docker-compose.full.yaml` only when you need local setup/admin access. -Prefer Podman? Same image, one command. No compose file needed. - -```bash -mkdir -p data/opencode local-cache/opencode workspace - -podman start holycode 2>/dev/null || podman run -d \ - --name holycode \ - --restart unless-stopped \ - --shm-size=2g \ - -p 4096:4096 \ - -v ./data/opencode:/home/opencode \ - -v ./local-cache/opencode:/home/opencode/.cache/opencode \ - -v ./workspace:/workspace \ - -e PUID=$(id -u) \ - -e PGID=$(id -g) \ - -e ANTHROPIC_API_KEY=your-key-here \ - docker.io/coderluii/holycode:latest -``` - -Open http://localhost:4096. You're in. +> `GIT_USER_NAME` and `GIT_USER_EMAIL` are only applied on first boot. To re-apply, delete the sentinel file and restart: `docker exec holycode rm /home/opencode/.config/opencode/.holycode-bootstrapped` then `docker compose restart`.

back to top @@ -512,6 +515,7 @@ Includes Liberation, DejaVu, Noto, and Noto Color Emoji fonts for correct page r |---------|---------| | Hermes Agent | Self-improving meta-agent with MCP, messaging adapters, and OpenCode delegation | | Paperclip | Local agent board that hires OpenCode workers and wakes them on heartbeat | +| CLIProxyAPI sidecar | Optional full-compose profile for centralized OpenAI-compatible account/model routing | | Claude Code CLI | Installed for Claude subscription auth flows via `ENABLE_CLAUDE_AUTH` |

@@ -536,10 +540,11 @@ s6-overlay supervises OpenCode and Xvfb. If a process crashes, it restarts autom ## 🧩 Bundled Services -HolyCode now ships with two optional layers on top of OpenCode. You do **not** need them to use the container. But if plain OpenCode gives you the hands, these two give you a brain and a control room. +HolyCode now ships with three optional layers on top of OpenCode. You do **not** need them to use the container. But if plain OpenCode gives you the hands, these add coordination, a control room, and centralized model routing. - **Hermes Agent** is for when you want a smarter coordinator sitting above OpenCode. - **Paperclip** is for when you want a board, a workflow, and actual agent management instead of just one-off prompts. +- **CLIProxyAPI** is for when you want one OpenAI-compatible endpoint that can route models/accounts through a separate sidecar. Flip the env var, restart the container, and the service comes up alongside the normal web UI. @@ -586,10 +591,42 @@ environment: - ENABLE_PAPERCLIP=true - PAPERCLIP_PORT=3100 - PAPERCLIP_DEPLOYMENT_MODE=authenticated + - PAPERCLIP_ALLOWED_HOSTNAMES=192.168.1.50,my-host.local ``` Paperclip state lives under `/home/opencode/.paperclip`. HolyCode bootstraps it in `authenticated` mode so Docker port publishing works cleanly. Open the dashboard, set up your company, and hire OpenCode-backed employees from there. +When opening Paperclip from another machine, set `PAPERCLIP_ALLOWED_HOSTNAMES` to the hostname or IP from the browser URL, without `http://`, `https://`, or `:3100`. Use commas for multiple values and restart the container after changes. This only allowlists those private hostnames; it does not make Paperclip public or disable authentication. + +### CLIProxyAPI + +CLIProxyAPI is the "model router" option. It runs as an optional sidecar from the full Compose reference and exposes an OpenAI-compatible API on port `8317` inside the Docker network. HolyCode can add a separate OpenCode provider named `cliproxyapi` that points at that sidecar. + +Why that matters: + +- **One provider surface.** OpenCode can use `cliproxyapi/` while CLIProxyAPI handles the account/model routing behind it. +- **Isolated from Claude Auth.** This does not replace `ENABLE_CLAUDE_AUTH`, does not touch `/home/opencode/.claude`, and does not set global Anthropic proxy variables. +- **No default host exposure.** The sidecar is reachable by HolyCode over Docker DNS as `http://cliproxyapi:8317/v1`; host port publishing is loopback-only and commented out by default. +- **Separate state.** Config, auth, and logs live under `./local-cache/cliproxyapi-*`, not under the OpenCode home or Claude credential folder. + +Turn it on with: + +```yaml +environment: + - CLIPROXYAPI_ENABLED=true + - CLIPROXYAPI_BASE_URL=http://cliproxyapi:8317/v1 + - CLIPROXYAPI_API_KEY= + - CLIPROXYAPI_MODEL=your-model-id +``` + +Then start the full Compose profile: + +```bash +docker compose -f docker-compose.full.yaml --profile cliproxyapi up -d +``` + +Create `./local-cache/cliproxyapi-config/config.yaml` before enabling the profile. Put CLIProxyAPI API keys/OAuth state in its own mounted config/auth paths; do not reuse `/home/opencode/.claude`. +

back to top

@@ -611,6 +648,7 @@ graph TD G --> I[opencode web :4096] G --> Q[Hermes API :8642] G --> R[Paperclip UI :3100] + A --> U[CLIProxyAPI sidecar :8317 internal] I --> J[Web UI] J --> K[Your Browser] I --> L[CLI Access] @@ -620,9 +658,10 @@ graph TD M --> P[opencode attach localhost:4096] Q --> S[Meta-agent API clients] R --> T[Agent board and CEO invite] + I --> U ``` -The entrypoint handles user remapping, plugin toggles, optional bundled-service toggles, and first-boot setup. s6-overlay supervises Xvfb, the OpenCode web server, and any optional bundled services you enabled. If a supervised process crashes, s6 restarts it automatically. Access the web UI at port 4096, Hermes on 8642, or Paperclip on 3100 when those services are enabled. +The entrypoint handles user remapping, plugin toggles, optional bundled-service toggles, CLIProxyAPI provider injection, and first-boot setup. s6-overlay supervises Xvfb, the OpenCode web server, and any optional bundled services you enabled inside the HolyCode container. The CLIProxyAPI sidecar is a separate Compose service. Access the web UI at port 4096, Hermes on 8642, or Paperclip on 3100 when those services are enabled.

back to top diff --git a/docs/podman.md b/docs/podman.md new file mode 100644 index 0000000..622058e --- /dev/null +++ b/docs/podman.md @@ -0,0 +1,200 @@ +# HolyCode with Podman + +Podman can run the same HolyCode image as Docker. Use this guide when you prefer a daemonless or rootless container runtime, especially on Fedora, RHEL, CoreOS, Rocky, AlmaLinux, or similar Linux hosts. + +This guide mirrors the minimal HolyCode web UI setup. For the full Docker Compose profile and sidecar services, use the main README and `docker-compose.full.yaml`. + +## What this guide covers + +- Running the HolyCode web UI with `podman run` +- Keeping OpenCode state, cache, and workspace files in bind mounts +- Loading provider keys from `.env` with `--env-file .env` +- SELinux labels for Fedora/RHEL/CoreOS hosts +- Rootless Podman permission and user namespace notes +- Optional service boundaries for Paperclip, Hermes, and CLIProxyAPI +- Safe update and recreate behavior + +## Prerequisites + +Install Podman on the host and run the command from the HolyCode project folder, or from another folder that contains the same `.env.example`, data, cache, and workspace paths. + +Copy the environment template and add at least one provider key: + +```bash +cp .env.example .env +``` + +Edit `.env` and set the provider you plan to use, for example `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, or another supported provider variable. + +Podman treats relative bind mount sources as paths relative to the directory where you run `podman`. Missing bind mount sources fail, so create them first. + +## Minimal web UI setup + +Create the host directories: + +```bash +mkdir -p ./data/opencode ./local-cache/opencode ./workspace +``` + +Run HolyCode: + +```bash +podman run -d \ + --name holycode \ + --restart unless-stopped \ + --shm-size=2g \ + -p 4096:4096 \ + -v ./data/opencode:/home/opencode \ + -v ./local-cache/opencode:/home/opencode/.cache/opencode \ + -v ./workspace:/workspace \ + --env-file .env \ + -e PUID=$(id -u) \ + -e PGID=$(id -g) \ + docker.io/coderluii/holycode:latest +``` + +Open http://localhost:4096. + +What the important options do: + +- `--shm-size=2g` gives Chromium and browser automation enough shared memory. +- `-p 4096:4096` publishes the OpenCode web UI. +- `./data/opencode:/home/opencode` persists OpenCode config, sessions, plugins, and service state. +- `./local-cache/opencode:/home/opencode/.cache/opencode` keeps plugin and package cache on local disk. +- `./workspace:/workspace` mounts your project files. +- `--env-file .env` loads provider keys and optional HolyCode toggles without putting secrets in shell history. +- `PUID` and `PGID` tell HolyCode which host UID/GID to use for file ownership inside mounted paths. +- `docker.io/coderluii/holycode:latest` fully qualifies the Docker Hub image for Podman. + +If you use a different host folder, keep the container paths unchanged. `/home/opencode`, `/home/opencode/.cache/opencode`, and `/workspace` are the paths HolyCode expects inside the container. + +## SELinux hosts + +On SELinux hosts such as Fedora, RHEL, or CoreOS, unlabeled bind mounts can look like permission problems from inside the container. Add `:Z` to each HolyCode bind mount for a private label used by this one container: + +```bash +podman run -d \ + --name holycode \ + --restart unless-stopped \ + --shm-size=2g \ + -p 4096:4096 \ + -v ./data/opencode:/home/opencode:Z \ + -v ./local-cache/opencode:/home/opencode/.cache/opencode:Z \ + -v ./workspace:/workspace:Z \ + --env-file .env \ + -e PUID=$(id -u) \ + -e PGID=$(id -g) \ + docker.io/coderluii/holycode:latest +``` + +Use `:z` only when the same host path must be shared by multiple containers. Do not casually relabel broad system paths or your entire home directory. + +## Rootless Podman and permissions + +Rootless Podman runs containers inside a user namespace. That is one of the main reasons people choose Podman, but it also means bind mount ownership can behave differently from Docker. + +Check these if files are unexpectedly owned or blocked: + +- Your user has ranges in `/etc/subuid` and `/etc/subgid`. +- `PUID=$(id -u)` and `PGID=$(id -g)` match the host user that should own files in `./workspace`. +- The host paths exist before running the container. +- SELinux hosts use `:Z` or `:z` labels as described above. + +Some rootless setups use custom user namespace modes such as `--userns=keep-id`. Do not add that flag by default for HolyCode; it changes how the container process user is mapped. Use it only after testing that it matches your host policy and does not interfere with HolyCode's `PUID`/`PGID` remapping. + +## Optional services + +The command above runs the minimal HolyCode web UI on port `4096`. + +Paperclip and Hermes can be enabled with the same environment variables used by the Docker Compose examples: + +```env +ENABLE_PAPERCLIP=true +PAPERCLIP_PORT=3100 +PAPERCLIP_DEPLOYMENT_MODE=authenticated +PAPERCLIP_ALLOWED_HOSTNAMES=192.168.1.50,my-host.local +ENABLE_HERMES=true +HERMES_PORT=8642 +``` + +If you enable them, publish the ports you need: + +```bash +-p 4096:4096 \ +-p 3100:3100 \ +-p 8642:8642 +``` + +Paperclip listens on `3100` when enabled. Hermes exposes an API service on `8642`; a `404` at `/` is normal as long as the process stays healthy. + +CLIProxyAPI is different. In HolyCode it is documented as a full Compose sidecar profile, not as part of the one-container Podman command. Use `docker-compose.full.yaml` when you need the supported CLIProxyAPI sidecar workflow. + +## Updating HolyCode + +Pull the latest image: + +```bash +podman pull docker.io/coderluii/holycode:latest +``` + +Then recreate the container: + +```bash +podman stop holycode +podman rm holycode +``` + +Run the `podman run` command again. Your data stays in `./data/opencode`, `./local-cache/opencode`, and `./workspace`. + +Do not use `podman start holycode` as an update path. It restarts the existing container with the old image, environment variables, ports, and mount settings. + +`--restart unless-stopped` restarts the container after normal exits unless you explicitly stopped it. Reboot persistence depends on Podman's `podman-restart.service`. If you later manage HolyCode through systemd or Quadlet, use systemd's `Restart=` behavior instead of Podman's `--restart` flag. + +## Troubleshooting + +### Permission denied on mounted files + +Verify `PUID`, `PGID`, host directory ownership, and rootless user namespace setup: + +```bash +id -u +id -g +``` + +Make sure those values match the `PUID` and `PGID` passed to the container. If rootless Podman still maps ownership unexpectedly, check `/etc/subuid` and `/etc/subgid` for your user. + +### SELinux AVC or denied access + +Add `:Z` to the three bind mounts for a private container label: + +```bash +-v ./data/opencode:/home/opencode:Z \ +-v ./local-cache/opencode:/home/opencode/.cache/opencode:Z \ +-v ./workspace:/workspace:Z +``` + +Use `:z` only when multiple containers must share the same host path. + +### Port 4096 already in use + +Publish HolyCode on a different host port while keeping the container port at `4096`: + +```bash +-p 4097:4096 +``` + +Then open http://localhost:4097. + +### Chromium or browser automation fails + +Make sure the command includes: + +```bash +--shm-size=2g +``` + +Without enough `/dev/shm`, Chromium can crash or produce broken automation results. + +### Environment variables did not change + +Changing `.env` does not update an already-created container. Stop and remove the container, then run the command again so Podman creates a new container with the updated environment.