# Deployment layouts One image, three roles (`JARVIS_ROLE`), selected in `.env`. GPU is added per OS via a compose override picked with `COMPOSE_FILE`. > `COMPOSE_FILE`'s file separator is OS-specific: Linux/macOS use `:`, Windows > uses `;` (a colon collides with the `C:` drive letter). Using `:` on Windows > yields `... The system cannot find the file specified`. If in doubt, leave > `COMPOSE_FILE` unset and pass the files explicitly: > `docker compose -f docker-compose.yml -f docker-compose.gpu-windows.yml up -d`. ## A. All-in-one (single machine) Everything (desktop + Chrome + bridge + bot + TTS) in one container. ``` # .env JARVIS_ROLE=full COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml # Ubuntu/macOS (":" ) # COMPOSE_FILE=docker-compose.yml;docker-compose.gpu-windows.yml # Windows 11 (";" ) DISCORD_SELFBOT_TOKEN=... DISCORD_GUILD_ID=... docker compose up -d # Ollama + javis (COMPOSE_FILE adds GPU) ``` ## B. Split: browser host (LAN) + bot on your PC The on-screen Chrome, real mouse/keyboard (xdotool) and screen live on the **browser host**. Your PC runs the **bot** and drives that browser over the internal network — no auth (internal only). ### Browser host (the LAN machine that shows Chrome, e.g. 192.168.10.5) ``` # .env JARVIS_ROLE=browser CDP_BIND=0.0.0.0 BROWSER_CONTROL_BIND=0.0.0.0 CDP_PUBLISH_BIND=0.0.0.0 # no GPU needed → leave COMPOSE_FILE unset (base compose only) docker compose up -d javis # desktop + Chrome + control-server (port 8777) ``` Watch it on this machine’s VNC (`localhost:5901`) / noVNC (`localhost:6080`). ### Bot host (your PC — Ubuntu or Windows 11) ``` # .env JARVIS_ROLE=bot BROWSER_CONTROL_URL=http://192.168.10.5:8777 # the browser host's LAN IP COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml # Ubuntu/macOS (":" ) # COMPOSE_FILE=docker-compose.yml;docker-compose.gpu-windows.yml # Windows 11 (";" ) DISCORD_SELFBOT_TOKEN=... DISCORD_GUILD_ID=... docker compose up -d # bot + bridge + TTS + Ollama (GPU per OS) ``` The bot’s `controlBrowser` tool posts commands to `BROWSER_CONTROL_URL`, so "네이버에서 X 검색", "구글로 돌아가" etc. drive the **browser host’s** Chrome with real human-style input (visible on its VNC). ### External Ollama (LLM on a separate host) To run Ollama on a different LAN machine (e.g. a dedicated GPU host at `192.168.10.9`) instead of inside this stack: ``` # .env on the app host OLLAMA_BASE_URL=http://192.168.10.9:11434 ``` Then start the app WITHOUT the in-stack LLM services: `docker compose up -d javis` (do not `docker compose up -d`, which would also start `ollama`/`ollama-init`). On the LLM host, make Ollama listen on the LAN (`OLLAMA_HOST=0.0.0.0:11434`) and pull the models there (`ollama pull `, ``, ``). The compose respects `OLLAMA_BASE_URL`, defaulting to the in-stack `ollama` service when unset. ## C. GPU split: brain host (.9, GPU) + app host (.5, no GPU) All the GPU-heavy compute (Whisper STT, the Ollama LLM, and TTS) runs on the **brain host** (a GPU box, e.g. `192.168.10.9`). The **app host** (no GPU, e.g. `192.168.10.5`) runs the Discord bot and the on-screen browser, and offloads every STT/LLM/TTS call to the brain over the LAN. Flow: the bot (app host) POSTs captured voice to the brain's bridge; the brain runs STT -> LLM -> TTS on the GPU and returns the reply audio; when the LLM drives the browser, the brain's `controlBrowser` tool POSTs to the app host's control-server (port 8777), so Chrome moves on the app host's screen. ### Brain host (.9 — GPU: STT + LLM + TTS) ``` # .env JARVIS_ROLE=brain BRIDGE_HOST=0.0.0.0 # bridge listens on the LAN SETTINGS_PUBLISH_BIND=0.0.0.0 # publish bridge port 8765 to the LAN OLLAMA_BASE_URL=http://192.168.10.9:11434 # Ollama on this host (bind 0.0.0.0) OLLAMA_CHAT_MODEL=qwen2.5:7b WHISPER_DEVICE=cuda WHISPER_COMPUTE_TYPE=float16 BROWSER_CONTROL_URL=http://192.168.10.5:8777 # drive the app host's Chrome COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml # GPU (":" on Linux) # Ollama runs on this host (systemd or a container) listening on 0.0.0.0:11434. docker compose up -d javis # brain bridge only (no bot, no desktop) ``` ### App host (.5 — bot + browser, no GPU) ``` # .env JARVIS_ROLE=app BRIDGE_URL=http://192.168.10.9:8765 # offload STT/LLM/TTS to the brain CDP_PUBLISH_BIND=0.0.0.0 # publish control-server 8777 to the LAN STREAM_HW=0 # no NVENC on a no-GPU host DISCORD_SELFBOT_TOKEN=... DISCORD_GUILD_ID=... # no GPU → leave COMPOSE_FILE unset (base compose only) docker compose up -d javis # bot + desktop + Chrome + control-server ``` The `brain` and `app` roles are backward-compatible additions to `full`/`browser` /`bot`; see `docker/run-if-role.sh`. ## Windows 11 notes - Install the NVIDIA driver on Windows and enable GPU in Docker Desktop (Settings → Resources → WSL Integration). Use the `gpu-windows.yml` override. - Paths: named volumes are cross-platform. The Gemini OAuth login (for `GEMINI_AUTH=oauth`) is bind-mounted from the project-local `./docker/gemini-oauth` into the container's `~/.gemini`. A project-relative path is used so it resolves the same on Windows Docker Desktop and Linux (`${HOME}` is often unset when compose runs from PowerShell/cmd). Seed it once from a machine with a browser and the logged-in Gemini CLI (`npm i -g @google/gemini-cli`, then `gemini` -> "Sign in with Google"), copying the login state: (Note: as of 2026-06 Google blocks personal Google accounts on this CLI login with "This client is no longer supported for Gemini Code Assist for individuals". Workspace/org accounts may still work; personal accounts should use `GEMINI_AUTH=apikey` with a key from https://aistudio.google.com/app/apikey instead. Real-time search fail-opens to DDG/Brave/Wikipedia either way.) `cp -r ~/.gemini/. docker/gemini-oauth/`. The essential file is `oauth_creds.json` (it holds the refresh token; `GOOGLE_GENAI_USE_GCA=true` forces OAuth, so that is the file the startup readiness check looks for) - copying the whole dir simply also carries the cached account/settings. To reuse an existing host login without copying, set `GEMINI_OAUTH_DIR=~/.gemini` in `.env`. If unseeded, real-time search fail-opens to DDG/Brave and the container logs a `🔑` warning on startup. ## Known limitation Discord Go-Live broadcast of the **browser host's** screen from a **remote** bot is not supported (the bot's WebRTC screen capture is local to the bot machine). Use the browser host's VNC to view it. A full remote-broadcast path is separate, larger work.