|
1 | 1 | # Running OpenConcho in Docker |
2 | 2 |
|
3 | | -The `@openconcho/web` SPA can be served from a container. The image is a |
4 | | -two-stage build: Node + pnpm builds the static bundle, then |
5 | | -`nginx-unprivileged` serves it on port `8080` as a non-root user. |
| 3 | +The `@openconcho/web` SPA ships as a container: a two-stage build (Node + pnpm |
| 4 | +builds the static bundle, then `nginx-unprivileged` serves it on port `8080` as |
| 5 | +a non-root user) that also **reverse-proxies the Honcho API under its own |
| 6 | +origin**, so the browser never makes a cross-origin request. |
6 | 7 |
|
7 | | -## Build and run |
| 8 | +## Add it to a Honcho Compose stack (recommended) |
8 | 9 |
|
9 | | -```bash |
10 | | -docker build -t openconcho-web . |
11 | | -docker run --rm -p 8080:8080 openconcho-web |
12 | | -# → http://localhost:8080 |
| 10 | +Honcho's self-hosting path is Docker Compose. Drop the `openconcho` service from |
| 11 | +[`docker-compose.yml`](../docker-compose.yml) into the project that runs your |
| 12 | +Honcho `api`: |
| 13 | + |
| 14 | +```yaml |
| 15 | +services: |
| 16 | + openconcho: |
| 17 | + image: ghcr.io/offendingcommit/openconcho-web:latest |
| 18 | + environment: |
| 19 | + HONCHO_UPSTREAM: http://api:8000 # nginx proxies /v3 + /health here |
| 20 | + OPENCONCHO_DEFAULT_HONCHO_URL: same-origin |
| 21 | + ports: |
| 22 | + - "127.0.0.1:8080:8080" |
| 23 | + depends_on: |
| 24 | + api: |
| 25 | + condition: service_healthy |
| 26 | + restart: unless-stopped |
13 | 27 | ``` |
14 | 28 |
|
15 | | -Hardened run (read-only filesystem, no added capabilities): |
| 29 | +`OPENCONCHO_DEFAULT_HONCHO_URL: same-origin` makes the UI default its Honcho |
| 30 | +base URL to its own origin, so API calls go through the proxy → **no browser |
| 31 | +CORS, and the API token never leaves the origin.** The published image is |
| 32 | +multi-arch (amd64 + arm64); the first publish creates a private GHCR package — |
| 33 | +make it public if you want unauthenticated pulls. |
| 34 | + |
| 35 | +## Standalone |
16 | 36 |
|
17 | 37 | ```bash |
18 | | -docker run --rm -p 8080:8080 \ |
19 | | - --read-only \ |
20 | | - --tmpfs /tmp \ |
21 | | - --tmpfs /var/cache/nginx \ |
22 | | - --cap-drop ALL \ |
23 | | - --security-opt no-new-privileges \ |
24 | | - openconcho-web |
| 38 | +docker build -t openconcho-web . |
| 39 | +docker run --rm -p 8080:8080 -e HONCHO_UPSTREAM=http://host.docker.internal:8000 openconcho-web |
| 40 | +# → http://localhost:8080 · GET /healthz returns "ok" |
25 | 41 | ``` |
26 | 42 |
|
27 | | -`GET /healthz` returns `200 ok` for container health checks. |
| 43 | +Runtime knobs (no rebuild needed): |
28 | 44 |
|
29 | | -## CORS |
| 45 | +| Env | Default | Meaning | |
| 46 | +|-----|---------|---------| |
| 47 | +| `HONCHO_UPSTREAM` | `http://api:8000` | Where nginx proxies `/v3` and `/health` | |
| 48 | +| `OPENCONCHO_DEFAULT_HONCHO_URL` | `same-origin` | SPA's default base URL — `same-origin`, an absolute URL, or empty (configure in Settings) | |
30 | 49 |
|
31 | | -The desktop app routes HTTP through Rust (`reqwest`), so it is **not** subject |
32 | | -to browser CORS. The **web build is**: it uses the browser's `fetch`, so every |
33 | | -request to your Honcho API is cross-origin. Honcho calls are `POST` + |
34 | | -`application/json` + `Authorization: Bearer`, which the browser always |
35 | | -**preflights** (`OPTIONS`). You must handle this one of two ways. |
| 50 | +Hardened run adds `--read-only --cap-drop ALL --security-opt no-new-privileges` |
| 51 | +with `--tmpfs /tmp --tmpfs /var/cache/nginx`. Note: the runtime config writes |
| 52 | +`config.js` into the web root at start, which a read-only root blocks — under |
| 53 | +`--read-only` either bind-mount `config.js` or leave |
| 54 | +`OPENCONCHO_DEFAULT_HONCHO_URL` empty and set the URL in Settings. |
36 | 55 |
|
37 | | -### Option 1 — configure Honcho's CORS (recommended) |
| 56 | +## CORS, the short version |
38 | 57 |
|
39 | | -Honcho is a FastAPI service. Allow the UI's origin via its |
40 | | -`CORSMiddleware` so preflight and actual requests succeed: |
| 58 | +The desktop app routes HTTP through Rust and bypasses browser CORS; the web |
| 59 | +build doesn't. The Compose setup above **solves CORS via the same-origin proxy** |
| 60 | +— nothing to configure on Honcho. If instead you point the UI at a *different* |
| 61 | +origin (absolute `OPENCONCHO_DEFAULT_HONCHO_URL` or a URL typed in Settings), |
| 62 | +allow that origin in Honcho's FastAPI `CORSMiddleware`: |
41 | 63 |
|
42 | 64 | ```python |
43 | | -app.add_middleware( |
44 | | - CORSMiddleware, |
45 | | - allow_origins=["http://localhost:8080"], # the OpenConcho origin |
46 | | - allow_methods=["*"], |
47 | | - allow_headers=["*"], |
48 | | -) |
| 65 | +app.add_middleware(CORSMiddleware, allow_origins=["https://your-ui-origin"], |
| 66 | + allow_methods=["*"], allow_headers=["*"]) |
49 | 67 | ``` |
50 | | - |
51 | | -This fits OpenConcho's model directly — the UI keeps using the absolute Honcho |
52 | | -URL you enter in Settings (stored in `localStorage`). Since you self-host |
53 | | -Honcho, you control this. |
54 | | - |
55 | | -### Option 2 — same-origin reverse proxy (advanced) |
56 | | - |
57 | | -Proxy the Honcho API under the same origin that serves the SPA, so the browser |
58 | | -sees same-origin requests and CORS never applies (the token also never crosses |
59 | | -origins). Uncomment the `location /honcho/` block in |
60 | | -[`docker/nginx.conf`](../docker/nginx.conf) and set `proxy_pass` to your Honcho |
61 | | -host. |
62 | | - |
63 | | -Caveat: the Settings form currently validates the base URL as an **absolute** |
64 | | -URL (`z.string().url()`), so pointing the UI at a relative same-origin path |
65 | | -(`/honcho`) isn't wired yet. Until that lands, Option 1 is the supported path. |
|
0 commit comments