# Running VulnClaw in Docker The image bundles the Python CLI, the built React Web UI, and the runtimes (`npx` + `uvx`) needed by the default MCP servers (`memory`, `fetch`). All mutable state (config, sessions, targets, reports) is kept in a `/data` volume. ## Quick start (docker compose) ```bash cp .env.example .env # add your VULNCLAW_LLM_API_KEY docker compose up --build # builds the image and starts the Web UI ``` Then open . State persists in the `vulnclaw-data` named volume across restarts. ## Quick start (plain docker) ```bash # Build docker build -t vulnclaw:latest . # Run the Web UI (persisting state in a named volume) docker run --rm -it \ -p 127.0.0.1:7788:7788 \ -e VULNCLAW_LLM_API_KEY=sk-your-key-here \ -v vulnclaw-data:/data \ vulnclaw:latest ``` ## Running CLI commands instead of the Web UI The entrypoint is the `vulnclaw` binary, so you can override the command: ```bash # One-off scan (replace TARGET) docker run --rm -it \ -e VULNCLAW_LLM_API_KEY=sk-your-key-here \ -v vulnclaw-data:/data \ vulnclaw:latest scan TARGET # Interactive REPL / TUI docker run --rm -it \ -e VULNCLAW_LLM_API_KEY=sk-your-key-here \ -v vulnclaw-data:/data \ vulnclaw:latest repl # Show help / available subcommands docker run --rm vulnclaw:latest --help ``` With compose: ```bash docker compose run --rm vulnclaw scan TARGET ``` ## Configuration Configuration is supplied via environment variables (see `.env.example`) and/or the persisted `config.yaml` written into the `/data` volume. Environment variables override the config file at startup. ## Scanning a target from inside the container `localhost` / `127.0.0.1` inside the container refers to the **container itself**, not your host or another container — so scanning `localhost:PORT` will never reach a service running elsewhere. Use a routable address instead: - **Target on your host** (e.g. a `pnpm dev` / `npm run dev` server): add a host-gateway alias and target `host.docker.internal`: ```yaml services: vulnclaw: extra_hosts: - "host.docker.internal:host-gateway" ``` Then scan `host.docker.internal:3000`. Note many dev servers bind to loopback only (`127.0.0.1` / `[::1]`); start them on `0.0.0.0` (e.g. `--host 0.0.0.0`) or the container still won't reach them. - **Target in another container**: put both on the same Docker network and scan it by **container/service name** (e.g. `targetapp:8080`): ```yaml services: vulnclaw: networks: [default, targetnet] networks: targetnet: external: true name: # `docker network ls` ``` Verify reachability before scanning: ```bash docker compose exec vulnclaw \ python -c "import socket; socket.create_connection(('TARGET', PORT), 3); print('reachable')" ``` ## Notes - The container binds the Web UI to `0.0.0.0` internally (required for the published port to be reachable); the host-side `127.0.0.1:7788` mapping keeps it private to your machine. Change the mapping to expose it elsewhere — only do so on networks you trust, as the UI has no authentication. - The `chrome-devtools` MCP server is disabled by default; enabling it requires a Chrome/Chromium browser which is not installed in this image to keep it lean. - The optional knowledge-base feature (`kb`, ChromaDB) is not installed by default. Add `kb` to the `pip install` extras in the Dockerfile if you need it.