The `docker run` example for `gateway` in `docs/deployment.md` had drifted from the canonical configuration in `docker-compose.yml`: - It omitted the security flags that `docker-compose.yml` already declares (`cap_drop: ALL` + `cap_add: SYS_ADMIN` + unconfined apparmor/seccomp). These are required whenever `tools.exec.sandbox: "bwrap"` is enabled, because bwrap needs CAP_SYS_ADMIN for user namespaces; without them bwrap exits with `clone3: Operation not permitted` and exec tools silently fail. - It omitted `-p 8765:8765`, even though both the bundled `docker-compose.yml` and `Dockerfile` (`EXPOSE 18790 8765`) already expose the WebSocket channel / WebUI port; users following the docs would get a reachable gateway health endpoint but an unreachable WebUI. This change keeps the two paths in sync so anyone reading deployment.md and using `docker run` directly gets the same security posture and port surface as the Compose path. Also adds a short `!IMPORTANT` note documenting that `gateway.host` and `channels.websocket.host` default to `127.0.0.1` (set in `nanobot/config/schema.py:GatewayConfig`). Docker `-p` cannot forward to the container's loopback interface, so the user must set both binds to `0.0.0.0` in `config.json` for the published ports to actually be reachable. This is the symptom reported as items 2 + 3 of #3873; items 1 + 4 of that issue are already resolved on `main` (`Dockerfile` line 49 already exposes both ports, and README.md lines 218-220 already reflect that the WebUI ships in the wheel). Docs only, no code changes. Signed-off-by: voidborne-d <258577966+voidborne-d@users.noreply.github.com>
6.3 KiB
Deployment
Docker
Tip
The
-v ~/.nanobot:/home/nanobot/.nanobotflag mounts your local config directory into the container, so your config and workspace persist across container restarts. The container runs as the non-root usernanobot(UID 1000) and reads config from/home/nanobot/.nanobot. Always mount your host config directory to/home/nanobot/.nanobot, not/root/.nanobot. If you get Permission denied, fix ownership on the host first:sudo chown -R 1000:1000 ~/.nanobot, or pass--user $(id -u):$(id -g)to match your host UID. Podman users can use--userns=keep-idinstead.[!IMPORTANT] Official Docker usage currently means building from this repository with the included
Dockerfile. Docker Hub images under third-party namespaces are not maintained or verified by HKUDS/nanobot; do not mount API keys or bot tokens into them unless you trust the publisher.
Important
The gateway and WebSocket channel default to
host: "127.0.0.1"inconfig.json(set innanobot/config/schema.py). Docker-pport forwarding cannot reach a container's loopback interface, so for the host or LAN to reach the exposed ports you must set both binds to0.0.0.0in~/.nanobot/config.jsonbefore starting the container:{ "gateway": { "host": "0.0.0.0" }, "channels": { "websocket": { "host": "0.0.0.0" } } }When
hostis0.0.0.0, the gateway refuses to start unlesstokenortokenIssueSecretis also configured on the WebSocket channel — seewebui/README.mdfor details.
Docker Compose
docker compose run --rm nanobot-cli onboard # first-time setup
vim ~/.nanobot/config.json # add API keys
docker compose up -d nanobot-gateway # start gateway
docker compose run --rm nanobot-cli agent -m "Hello!" # run CLI
docker compose logs -f nanobot-gateway # view logs
docker compose down # stop
Docker
# Build the image
docker build -t nanobot .
# Initialize config (first time only)
docker run -v ~/.nanobot:/home/nanobot/.nanobot --rm nanobot onboard
# Edit config on host to add API keys
vim ~/.nanobot/config.json
# Run gateway (connects to enabled channels, e.g. Telegram/Discord/Mochat).
# Mirrors the security caps and port mappings declared in docker-compose.yml:
# - `--cap-drop ALL --cap-add SYS_ADMIN` + unconfined apparmor/seccomp are required
# when `tools.exec.sandbox: "bwrap"` is enabled (bwrap needs CAP_SYS_ADMIN for
# user namespaces). Without them, `bwrap` exits with `clone3: Operation not permitted`.
# - `-p 8765:8765` exposes the WebSocket channel / WebUI alongside the gateway health
# endpoint on 18790.
docker run \
--cap-drop ALL --cap-add SYS_ADMIN \
--security-opt apparmor=unconfined \
--security-opt seccomp=unconfined \
-v ~/.nanobot:/home/nanobot/.nanobot \
-p 18790:18790 -p 8765:8765 \
nanobot gateway
# Or run a single command
docker run -v ~/.nanobot:/home/nanobot/.nanobot --rm nanobot agent -m "Hello!"
docker run -v ~/.nanobot:/home/nanobot/.nanobot --rm nanobot status
Linux Service
Run the gateway as a systemd user service so it starts automatically and restarts on failure.
1. Find the nanobot binary path:
which nanobot # e.g. /home/user/.local/bin/nanobot
2. Create the service file at ~/.config/systemd/user/nanobot-gateway.service (replace ExecStart path if needed):
[Unit]
Description=Nanobot Gateway
After=network.target
[Service]
Type=simple
ExecStart=%h/.local/bin/nanobot gateway
Restart=always
RestartSec=10
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=%h
[Install]
WantedBy=default.target
3. Enable and start:
systemctl --user daemon-reload
systemctl --user enable --now nanobot-gateway
Common operations:
systemctl --user status nanobot-gateway # check status
systemctl --user restart nanobot-gateway # restart after config changes
journalctl --user -u nanobot-gateway -f # follow logs
If you edit the .service file itself, run systemctl --user daemon-reload before restarting.
Note: User services only run while you are logged in. To keep the gateway running after logout, enable lingering:
loginctl enable-linger $USER
macOS LaunchAgent
Use a LaunchAgent when you want nanobot gateway to stay online after you log in, without keeping a terminal open.
1. Get the absolute nanobot path:
which nanobot # e.g. /Users/youruser/.local/bin/nanobot
Use that exact path in the plist. It keeps the Python environment from your install method.
2. Create ~/Library/LaunchAgents/ai.nanobot.gateway.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.nanobot.gateway</string>
<key>ProgramArguments</key>
<array>
<string>/Users/youruser/.local/bin/nanobot</string>
<string>gateway</string>
<string>--workspace</string>
<string>/Users/youruser/.nanobot/workspace</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/youruser/.nanobot/workspace</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>StandardOutPath</key>
<string>/Users/youruser/.nanobot/logs/gateway.log</string>
<key>StandardErrorPath</key>
<string>/Users/youruser/.nanobot/logs/gateway.error.log</string>
</dict>
</plist>
3. Load and start it:
mkdir -p ~/Library/LaunchAgents ~/.nanobot/logs
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.nanobot.gateway.plist
launchctl enable gui/$(id -u)/ai.nanobot.gateway
launchctl kickstart -k gui/$(id -u)/ai.nanobot.gateway
Common operations:
launchctl list | grep ai.nanobot.gateway
launchctl kickstart -k gui/$(id -u)/ai.nanobot.gateway # restart
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/ai.nanobot.gateway.plist
After editing the plist, run launchctl bootout ... and launchctl bootstrap ... again.
Note: if startup fails with "address already in use", stop the manually started
nanobot gatewayprocess first.