Files
openconcho/docs/docker.md
Offending Commit 9a35be7b15 docs: document the /api proxy contract and env vars
Retire HONCHO_UPSTREAM and the same-origin sentinel; document the per-request
X-Honcho-Upstream header model, OPENCONCHO_DEFAULT_HONCHO_URL seeding, and the
optional OPENCONCHO_UPSTREAM_ALLOWLIST SSRF guard across compose, README,
AGENTS.md, and docs/docker.md.
2026-06-02 13:16:30 -05:00

3.5 KiB

Running OpenConcho in Docker

The @openconcho/web SPA ships as a container: a two-stage build (Node + pnpm builds the static bundle, then nginx-unprivileged serves it on port 8080 as a non-root user) that also reverse-proxies the Honcho API under its own origin, so the browser never makes a cross-origin request.

How the proxy works

The browser issues every Honcho call same-origin to /api/* and names the real upstream per request in an X-Honcho-Upstream header (sourced from the active instance's base URL). nginx strips /api, forwards to that upstream server-side, and returns the response. Because the browser→nginx hop is same-origin, no CORS applies; the nginx→Honcho hop is server-side, where CORS is irrelevant. The frontend stays the source of truth for which instance to talk to, so the multi-instance switcher and the Fleet view keep working.

Honcho's self-hosting path is Docker Compose. Drop the openconcho service from docker-compose.yml into the project that runs your Honcho api:

services:
  openconcho:
    image: ghcr.io/offendingcommit/openconcho-web:latest
    environment:
      # Seeds the first instance; nginx resolves this on the compose network.
      OPENCONCHO_DEFAULT_HONCHO_URL: http://api:8000
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      api:
        condition: service_healthy
    restart: unless-stopped

OPENCONCHO_DEFAULT_HONCHO_URL seeds the UI's first instance with an absolute URL. The browser sends that URL in the X-Honcho-Upstream header; nginx (on the compose network) forwards to it — no browser CORS, and the API token never leaves the origin. The published image is multi-arch (amd64 + arm64); the first publish creates a private GHCR package — make it public for unauthenticated pulls.

Standalone

docker build -t openconcho-web .
docker run --rm -p 8080:8080 -e OPENCONCHO_DEFAULT_HONCHO_URL=http://host.docker.internal:8000 openconcho-web
# → http://localhost:8080  ·  GET /healthz returns "ok"

Runtime knobs (no rebuild needed):

Env Default Meaning
OPENCONCHO_DEFAULT_HONCHO_URL (empty) Absolute URL seeding the first instance; empty = configure in Settings
OPENCONCHO_UPSTREAM_ALLOWLIST (empty) Optional SSRF guard: comma-separated host globs (e.g. honcho.example.net,*.honcho.dev). Empty = forward anywhere

Hardened run adds --read-only --cap-drop ALL --security-opt no-new-privileges with --tmpfs /tmp --tmpfs /var/cache/nginx. Note: the entrypoint writes config.js and the allowlist map at start, which a read-only root blocks — under --read-only either bind-mount those paths or leave the env empty and configure the URL in Settings.

SSRF: when to set the allowlist

The header-driven proxy forwards to whatever upstream the client names. With the default 127.0.0.1:8080 binding only your own machine can reach nginx, so leaving the allowlist open is fine. Before exposing the proxy (e.g. behind a tunnel), set OPENCONCHO_UPSTREAM_ALLOWLIST to the host globs you trust — non-matching upstreams are rejected with 403 and an X-Honcho-Proxy-Reject: allowlist header.

CORS, the short version

The desktop app routes HTTP through Rust (reqwest) and bypasses browser CORS; the web build solves it with the same-origin /api proxy above — nothing to configure on Honcho. The proxy makes a Honcho-side CORSMiddleware unnecessary regardless of which instance you point at.