2026-05-28 13:59:47 -05:00
|
|
|
# Running OpenConcho in Docker
|
|
|
|
|
|
2026-05-28 16:06:03 -05:00
|
|
|
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.
|
2026-05-28 13:59:47 -05:00
|
|
|
|
2026-06-02 13:16:30 -05:00
|
|
|
## 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.
|
|
|
|
|
|
2026-06-02 14:02:36 -05:00
|
|
|
## Compose: dev vs prod profiles
|
2026-05-28 13:59:47 -05:00
|
|
|
|
2026-06-02 14:02:36 -05:00
|
|
|
One [`docker-compose.yml`](../docker-compose.yml) with two profiles; the shared
|
|
|
|
|
config (env, ports, extra_hosts) is defined once via a YAML anchor:
|
2026-06-02 13:51:23 -05:00
|
|
|
|
2026-06-02 14:02:36 -05:00
|
|
|
- **`dev` profile** — `build: .`, runs **your local source** (`make up`).
|
|
|
|
|
- **`prod` profile** — pulls the **published image**
|
|
|
|
|
(`ghcr.io/offendingcommit/openconcho-web:latest`, `pull_policy: always`) (`make prod`).
|
2026-06-02 13:51:23 -05:00
|
|
|
|
|
|
|
|
```bash
|
2026-06-02 13:59:04 -05:00
|
|
|
make up # build from source + run → http://localhost:8080
|
|
|
|
|
make prod # pull ghcr…:latest instead of building
|
2026-06-02 14:02:36 -05:00
|
|
|
make down # stop + remove (either profile)
|
2026-06-02 13:59:04 -05:00
|
|
|
make clean # down + drop the locally built image
|
2026-06-02 13:51:23 -05:00
|
|
|
```
|
|
|
|
|
|
2026-06-02 14:02:36 -05:00
|
|
|
`make up` expands to `docker compose --profile dev up -d --build` and `make prod`
|
|
|
|
|
to `docker compose --profile prod up -d`. A bare `docker compose up` (no profile)
|
|
|
|
|
starts nothing — use the make targets or pass `--profile`. Set env inline or via a
|
|
|
|
|
`.env` file:
|
2026-06-02 13:51:23 -05:00
|
|
|
|
|
|
|
|
```bash
|
2026-06-02 13:59:04 -05:00
|
|
|
OPENCONCHO_DEFAULT_HONCHO_URL=https://honcho.example.net make prod
|
2026-06-02 13:51:23 -05:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The published image is multi-arch (amd64 + arm64); the first publish creates a
|
|
|
|
|
private GHCR package — make it public for unauthenticated pulls.
|
|
|
|
|
|
|
|
|
|
## Add it to an existing Honcho Compose stack
|
|
|
|
|
|
|
|
|
|
Drop the `openconcho` service into the project that runs your Honcho `api`,
|
|
|
|
|
pointing the seed at the api service (nginx resolves it on the compose network):
|
2026-05-28 16:06:03 -05:00
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
services:
|
|
|
|
|
openconcho:
|
|
|
|
|
image: ghcr.io/offendingcommit/openconcho-web:latest
|
|
|
|
|
environment:
|
2026-06-02 13:16:30 -05:00
|
|
|
OPENCONCHO_DEFAULT_HONCHO_URL: http://api:8000
|
2026-05-28 16:06:03 -05:00
|
|
|
ports:
|
|
|
|
|
- "127.0.0.1:8080:8080"
|
|
|
|
|
depends_on:
|
|
|
|
|
api:
|
|
|
|
|
condition: service_healthy
|
|
|
|
|
restart: unless-stopped
|
2026-05-28 13:59:47 -05:00
|
|
|
```
|
|
|
|
|
|
2026-06-02 13:16:30 -05:00
|
|
|
`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
|
2026-06-02 13:51:23 -05:00
|
|
|
leaves the origin.**
|
2026-05-28 16:06:03 -05:00
|
|
|
|
2026-06-02 13:51:23 -05:00
|
|
|
## Standalone (no compose)
|
2026-05-28 13:59:47 -05:00
|
|
|
|
|
|
|
|
```bash
|
2026-06-02 13:51:23 -05:00
|
|
|
docker run --rm -p 8080:8080 -e OPENCONCHO_DEFAULT_HONCHO_URL=http://host.docker.internal:8000 \
|
|
|
|
|
ghcr.io/offendingcommit/openconcho-web:latest
|
2026-05-28 16:06:03 -05:00
|
|
|
# → http://localhost:8080 · GET /healthz returns "ok"
|
2026-05-28 13:59:47 -05:00
|
|
|
```
|
|
|
|
|
|
2026-05-28 16:06:03 -05:00
|
|
|
Runtime knobs (no rebuild needed):
|
2026-05-28 13:59:47 -05:00
|
|
|
|
2026-05-28 16:06:03 -05:00
|
|
|
| Env | Default | Meaning |
|
|
|
|
|
|-----|---------|---------|
|
2026-06-02 13:16:30 -05:00
|
|
|
| `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 |
|
2026-05-28 13:59:47 -05:00
|
|
|
|
2026-05-28 16:06:03 -05:00
|
|
|
Hardened run adds `--read-only --cap-drop ALL --security-opt no-new-privileges`
|
2026-06-02 13:16:30 -05:00
|
|
|
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.
|
2026-05-28 13:59:47 -05:00
|
|
|
|
2026-06-02 13:16:30 -05:00
|
|
|
## SSRF: when to set the allowlist
|
2026-05-28 13:59:47 -05:00
|
|
|
|
2026-06-02 13:16:30 -05:00
|
|
|
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.
|
2026-05-28 13:59:47 -05:00
|
|
|
|
2026-06-02 13:16:30 -05:00
|
|
|
## 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.
|