Hi all, Is OpenHands supported in a fully air-gapp...
# questions
f
Hi all, Is OpenHands supported in a fully air-gapped (no internet egress) environment, and if so, what's the recommended approach? When a conversation starts, I can see the runtime making outbound HTTPS (:443) connections before it's ready pulling python packages
c
cc: @fierce-account-55183
l
@nutritious-balloon-65098 create a docker container with OpenHands/agent-canvas installed, and then try to run it with no external network communication allowed at all. Will it work or will it die due to trying to access the network? Are there any mitigation strategies that could make it work entirely airgapped?
n
Uh oh! There was an unexpected error starting the job :(
l
@nutritious-balloon-65098 create a docker container with OpenHands/agent-canvas installed, and then try to run it with no external network communication allowed at all. Will it work or will it die due to trying to access the network? Are there any mitigation strategies that could make it work entirely airgapped?
n
Uh oh! There was an unexpected error starting the job :(
l
@nutritious-balloon-65098 create a docker container with OpenHands/agent-canvas installed, and then try to run it with no external network communication allowed at all. Will it work or will it die due to trying to access the network? Are there any mitigation strategies that could make it work entirely airgapped?
n
I'm on it! Graham Neubig can track my progress here.
l
Ugh, the slack resolver is not having a good day today..
πŸ‘€ 1
f
Possibly related to the current indicent
πŸ‘ 1
l
@nutritious-balloon-65098 create a docker container with OpenHands/agent-canvas installed, and then try to run it with no external network communication allowed at all. Will it work or will it die due to trying to access the network? Are there any mitigation strategies that could make it work entirely airgapped?
n
Uh oh! There was an unexpected error starting the job :(
f
Hi Team, wondering of the job ran. Is it possible to check again
l
Hi @fast-forest-2675, here’s an agent-driven response:
Copy code
## Answer to Guhan's air-gap question (from the Slack thread)

I dug into the actual `agent-canvas` Docker image (`docker/Dockerfile`, `docker/entrypoint.sh`), `config/defaults.json`, the OpenHands SDK runtime code (`software-agent-sdk`), and the agent-server base image Dockerfile. Here's the verdict.

### Short answer
**Not out of the box β€” it will die without mitigation β€” but it is achievable with a known set of config changes.** The outbound `:443` connections Guhan saw at conversation start come from a handful of well-defined places, all of which have knobs.

### Will it work or die with no external network?
- **Control plane (agent-server + automation + frontend) starts fine air-gapped.** The entrypoint only talks to `127.0.0.1` (agent-server `:18000`, automation `:18001`, static server `:8000`). No outbound calls on startup. βœ…
- **A conversation will die** on the first turn unless you fix these runtime network dependencies:

### The runtime `:443` calls and how to kill each

**1. LLM inference (the big one).** Every turn calls the LLM provider's HTTPS API. This is essential and *must* be served from inside the air-gap.
- *Mitigation:* Point `llm.base_url` at a self-hosted OpenAI-compatible endpoint inside the network (vLLM / SGLang / Ollama / LiteLLM proxy serving an open model). The repo's own mock-LLM E2E (`tests/e2e/mock-llm/scripts/mock-llm-server.py`) proves any OpenAI-compatible `base_url` works β€” set it via Settings β†’ LLM (Advanced view) or `LLM_BASE_URL`/`LLM_MODEL`/`LLM_API_KEY`. Note: the default model `openhands/minimax-m2.7` points at the All-Hands LLM proxy (`llm-proxy.app.all-hands.dev`) β€” that won't resolve air-gapped, so you must override.

**2. MCP servers launched via `npx`/`uvx`.** If an agent profile references an MCP server whose command is `npx -y <pkg>` or `uvx <pkg>`, the first connect fetches the package from npm/PyPI.
- *Mitigation:* The agent-server image already **pre-installs and pins** the ACP CLIs (`claude-agent-acp@0.44.0`, `codex-acp@0.46.0`, `gemini-cli@0.46.0`) at build time and rewrites `npx -y <pkg>` to run the local binary (no fetch) β€” see `acp_providers.py::_maybe_use_pinned_binary`. For any *other* MCP servers you use, either (a) pre-install the package into the image and point the MCP config at the local binary, or (b) avoid MCP servers that aren't vendored. The `github-mcp-server` Go binary is already baked into the agent-canvas image for the same reason.

**3. Browser tool β†’ Chromium download.** `browser_use/impl.py::_install_chromium()` runs `uvx playwright install chromium --with-deps` on first use if Chromium isn't found β€” this hits the Playwright CDN.
- *Mitigation:* Either set `VITE_ENABLE_BROWSER_TOOLS=false` to drop `BrowserToolSet` from new conversations, **or** pre-install Chromium into a custom runtime image (`uvx playwright install chromium` at build time) and bind-mount `~/.cache/ms-playwright`.

**4. Telemetry (PostHog / `z.openhands.dev`).** Two systems: frontend PostHog (`<http://us.i.posthog.com|us.i.posthog.com>`, keyed off `VITE_POSTHOG_CLIENT_KEY`) and library telemetry (`z.openhands.dev`).
- *Mitigation:* Omit `VITE_POSTHOG_CLIENT_KEY` (PostHogProvider never mounts), set `VITE_DO_NOT_TRACK=1`, and set `user_consents_to_analytics=false` in settings. The live E2E suite already does exactly this (`playwright.live.config.ts`).

**5. Cloud API key validation (`app.all-hands.dev`).** Without it, automation auth falls back to validating locally-generated session keys against the cloud API β†’ 401.
- *Mitigation:* Already handled by the entrypoint β€” `AUTOMATION_AGENT_SERVER_URL` defaults to `<http://127.0.0.1>:<agent-server-port>`, which flips automation into **local mode** (validates keys internally, no cloud call). Just don't unset it.

**6. Runtime image pull (only if you use DockerRuntime).** The agent-server base image ships **no Docker** ("No Docker, no VNC, no Desktop, no VSCode Web") and uses a **tmux-based local runtime** (`libtmux`, `TerminalTool`) β€” so by default there's **no `docker pull <http://ghcr.io/openhands/runtime`**|ghcr.io/openhands/runtime`**> at conversation start. If you instead configure a Docker/remote runtime, you must pre-pull the runtime image into a local registry or `docker load` it.

### Recommended air-gapped deployment recipe
1. **Build** the all-in-one image normally (build needs internet β€” that's fine; air-gap is a run-time constraint): `node scripts/docker-build.mjs`.
2. **Pre-bake** anything you'll need that normally fetches at runtime into a derived image: `uvx playwright install chromium`, any extra MCP server binaries, your model's tokenizer, etc.
3. **Stand up a local LLM** (vLLM/SGLang/Ollama/LiteLLM) serving an open model on the air-gap network.
4. **Run** the container with egress blocked (e.g. `--network none` if the LLM runs in the same container, or a restricted bridge network + iptables/egress-proxy allow-list to only the LLM host).
5. **Set env:** `LLM_BASE_URL`/`LLM_MODEL`/`LLM_API_KEY` β†’ local LLM; `VITE_DO_NOT_TRACK=1`; omit `VITE_POSTHOG_CLIENT_KEY`; keep `AUTOMATION_AGENT_SERVER_URL` default; `FILE_STORE=local`; `VITE_ENABLE_BROWSER_TOOLS=false` (unless you pre-baked Chromium). In Settings, set `user_consents_to_analytics=false`.
6. **Don't configure MCP servers** that need `npx`/`uvx` fetch at runtime β€” use the pre-installed ACP/GitHub ones or vendor your own.

### Bottom line for Guhan
Yes, OpenHands/agent-canvas *can* run fully air-gapped, but it requires a self-hosted LLM and a handful of env/config knobs to suppress the runtime fetches (LLM API, optional Chromium, optional MCP `npx`/`uvx`, telemetry). The `:443` traffic he observed at conversation start is almost certainly the **LLM provider call** (default points at the All-Hands proxy) plus, if enabled, **MCP `npx`/`uvx` package fetches** and/or the **Chromium download** β€” all addressable per above. The control-plane services themselves are air-gap-clean.

---
*Note: I was unable to empirically run the container with `--network none` in this environment β€” the system `dockerd` is running but my user isn't in the `docker` group and there's no passwordless sudo, and rootless Docker couldn't be set up (`uidmap`/`newuidmap` missing, can't `apt install` without sudo). The analysis above is grounded in the actual source of the Dockerfile, entrypoint, SDK runtime, and agent-server base image rather than a live `--network none` run. Graham, if you want, I can write this up as a `docs/AIR_GAPPED.md` and/or produce a hardened `Dockerfile.airgap` + sample `docker run` invocation in the agent-canvas repo.*
βœ… 1