# Docker (optional)
Docker is optional. Use it only if you want a containerized gateway or to validate the Docker flow.
# Is Docker right for me?
- Yes: you want an isolated, throwaway gateway environment or to run OpenSoul on a host without local installs.
- No: you’re running on your own machine and just want the fastest dev loop. Use the normal install flow instead.
- Sandboxing note: agent sandboxing uses Docker too, but it does not require the full gateway to run in Docker. See Sandboxing.
This guide covers:
- Containerized Gateway (full OpenSoul in Docker)
- Per-session Agent Sandbox (host gateway + Docker-isolated agent tools)
Sandboxing details: Sandboxing
# Requirements
- Docker Desktop (or Docker Engine) + Docker Compose v2
- Enough disk for images + logs
# Containerized Gateway (Docker Compose)
# Quick start (recommended)
From repo root:
./docker-setup.shThis script:
- builds the gateway image
- runs the onboarding wizard
- prints optional provider setup hints
- starts the gateway via Docker Compose
- generates a gateway token and writes it to
.env
Optional env vars:
OPENSOUL_DOCKER_APT_PACKAGES— install extra apt packages during buildOPENSOUL_EXTRA_MOUNTS— add extra host bind mountsOPENSOUL_HOME_VOLUME— persist/home/nodein a named volume
After it finishes:
- Open
http://127.0.0.1:18789/in your browser. - Paste the token into the Control UI (Settings → token).
- Need the URL again? Run
docker compose run --rm opensoul-cli dashboard --no-open.
It writes config/workspace on the host:
~/.opensoul/~/.opensoul/workspace
Running on a VPS? See Hetzner (Docker VPS).
# Manual flow (compose)
docker build -t opensoul:local -f Dockerfile .
docker compose run --rm opensoul-cli onboard
docker compose up -d opensoul-gatewayNote: run docker compose ... from the repo root. If you enabled OPENSOUL_EXTRA_MOUNTS or OPENSOUL_HOME_VOLUME, the setup script writes docker-compose.extra.yml; include it when running Compose elsewhere:
docker compose -f docker-compose.yml -f docker-compose.extra.yml <command># Control UI token + pairing (Docker)
If you see “unauthorized” or “disconnected (1008): pairing required”, fetch a fresh dashboard link and approve the browser device:
docker compose run --rm opensoul-cli dashboard --no-open
docker compose run --rm opensoul-cli devices list
docker compose run --rm opensoul-cli devices approve <requestId>More detail: Dashboard, Devices.
# Extra mounts (optional)
If you want to mount additional host directories into the containers, set OPENSOUL_EXTRA_MOUNTS before running docker-setup.sh. This accepts a comma-separated list of Docker bind mounts and applies them to both opensoul-gateway and opensoul-cli by generating docker-compose.extra.yml.
Example:
export OPENSOUL_EXTRA_MOUNTS="$HOME/.codex:/home/node/.codex:ro,$HOME/github:/home/node/github:rw"
./docker-setup.shNotes:
- Paths must be shared with Docker Desktop on macOS/Windows.
- If you edit
OPENSOUL_EXTRA_MOUNTS, rerundocker-setup.shto regenerate the extra compose file. docker-compose.extra.ymlis generated. Don’t hand-edit it.
# Persist the entire container home (optional)
If you want /home/node to persist across container recreation, set a named volume via OPENSOUL_HOME_VOLUME. This creates a Docker volume and mounts it at /home/node, while keeping the standard config/workspace bind mounts. Use a named volume here (not a bind path); for bind mounts, use OPENSOUL_EXTRA_MOUNTS.
Example:
export OPENSOUL_HOME_VOLUME="opensoul_home"
./docker-setup.shYou can combine this with extra mounts:
export OPENSOUL_HOME_VOLUME="opensoul_home"
export OPENSOUL_EXTRA_MOUNTS="$HOME/.codex:/home/node/.codex:ro,$HOME/github:/home/node/github:rw"
./docker-setup.shNotes:
- If you change
OPENSOUL_HOME_VOLUME, rerundocker-setup.shto regenerate the extra compose file. - The named volume persists until removed with
docker volume rm <name>.
# Install extra apt packages (optional)
If you need system packages inside the image (for example, build tools or media libraries), set OPENSOUL_DOCKER_APT_PACKAGES before running docker-setup.sh. This installs the packages during the image build, so they persist even if the container is deleted.
Example:
export OPENSOUL_DOCKER_APT_PACKAGES="ffmpeg build-essential"
./docker-setup.shNotes:
- This accepts a space-separated list of apt package names.
- If you change
OPENSOUL_DOCKER_APT_PACKAGES, rerundocker-setup.shto rebuild the image.
# Power-user / full-featured container (opt-in)
The default Docker image is security-first and runs as the non-root node user. This keeps the attack surface small, but it means:
- no system package installs at runtime
- no Homebrew by default
- no bundled Chromium/Playwright browsers
If you want a more full-featured container, use these opt-in knobs:
- Persist
/home/nodeso browser downloads and tool caches survive:
export OPENSOUL_HOME_VOLUME="opensoul_home"
./docker-setup.sh- Bake system deps into the image (repeatable + persistent):
export OPENSOUL_DOCKER_APT_PACKAGES="git curl jq"
./docker-setup.sh- Install Playwright browsers without
npx(avoids npm override conflicts):
docker compose run --rm opensoul-cli \
node /app/node_modules/playwright-core/cli.js install chromiumIf you need Playwright to install system deps, rebuild the image with OPENSOUL_DOCKER_APT_PACKAGES instead of using --with-deps at runtime.
- Persist Playwright browser downloads:
- Set
PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwrightindocker-compose.yml. - Ensure
/home/nodepersists viaOPENSOUL_HOME_VOLUME, or mount/home/node/.cache/ms-playwrightviaOPENSOUL_EXTRA_MOUNTS.
# Permissions + EACCES
The image runs as node (uid 1000). If you see permission errors on /home/node/.opensoul, make sure your host bind mounts are owned by uid 1000.
Example (Linux host):
sudo chown -R 1000:1000 /path/to/opensoul-config /path/to/opensoul-workspaceIf you choose to run as root for convenience, you accept the security tradeoff.
# Faster rebuilds (recommended)
To speed up rebuilds, order your Dockerfile so dependency layers are cached. This avoids re-running pnpm install unless lockfiles change:
FROM node:22-bookworm
# Install Bun (required for build scripts)
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:${PATH}"
RUN corepack enable
WORKDIR /app
# Cache dependencies unless package metadata changes
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY scripts ./scripts
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
RUN pnpm ui:install
RUN pnpm ui:build
ENV NODE_ENV=production
CMD ["node","dist/index.js"]# Channel setup (optional)
Use the CLI container to configure channels, then restart the gateway if needed.
WhatsApp (QR):
docker compose run --rm opensoul-cli channels loginTelegram (bot token):
docker compose run --rm opensoul-cli channels add --channel telegram --token "<token>"Discord (bot token):
docker compose run --rm opensoul-cli channels add --channel discord --token "<token>"Docs: WhatsApp, Telegram, Discord
# OpenAI Codex OAuth (headless Docker)
If you pick OpenAI Codex OAuth in the wizard, it opens a browser URL and tries to capture a callback on http://127.0.0.1:1455/auth/callback. In Docker or headless setups that callback can show a browser error. Copy the full redirect URL you land on and paste it back into the wizard to finish auth.
# Health check
docker compose exec opensoul-gateway node dist/index.js health --token "$OPENSOUL_GATEWAY_TOKEN"# E2E smoke test (Docker)
scripts/e2e/onboard-docker.sh# QR import smoke test (Docker)
pnpm test:docker:qr# Notes
- Gateway bind defaults to
lanfor container use. - Dockerfile CMD uses
--allow-unconfigured; mounted config withgateway.modenotlocalwill still start. Override CMD to enforce the guard. - The gateway container is the source of truth for sessions (
~/.opensoul/agents/<agentId>/sessions/).
# Agent Sandbox (host gateway + Docker tools)
Deep dive: Sandboxing
# What it does
When agents.defaults.sandbox is enabled, non-main sessions run tools inside a Docker container. The gateway stays on your host, but the tool execution is isolated:
- scope:
"agent"by default (one container + workspace per agent) - scope:
"session"for per-session isolation - per-scope workspace folder mounted at
/workspace - optional agent workspace access (
agents.defaults.sandbox.workspaceAccess) - allow/deny tool policy (deny wins)
- inbound media is copied into the active sandbox workspace (
media/inbound/*) so tools can read it (withworkspaceAccess: "rw", this lands in the agent workspace)
Warning: scope: "shared" disables cross-session isolation. All sessions share one container and one workspace.
# Per-agent sandbox profiles (multi-agent)
If you use multi-agent routing, each agent can override sandbox + tool settings: agents.list[].sandbox and agents.list[].tools (plus agents.list[].tools.sandbox.tools). This lets you run mixed access levels in one gateway:
- Full access (personal agent)
- Read-only tools + read-only workspace (family/work agent)
- No filesystem/shell tools (public agent)
See Multi-Agent Sandbox & Tools for examples, precedence, and troubleshooting.
# Default behavior
- Image:
opensoul-sandbox:bookworm-slim - One container per agent
- Agent workspace access:
workspaceAccess: "none"(default) uses~/.opensoul/sandboxes"ro"keeps the sandbox workspace at/workspaceand mounts the agent workspace read-only at/agent(disableswrite/edit/apply_patch)"rw"mounts the agent workspace read/write at/workspace
- Auto-prune: idle > 24h OR age > 7d
- Network:
noneby default (explicitly opt-in if you need egress) - Default allow:
exec,process,read,write,edit,sessions_list,sessions_history,sessions_send,sessions_spawn,session_status - Default deny:
browser,canvas,nodes,cron,discord,gateway
# Enable sandboxing
If you plan to install packages in setupCommand, note:
- Default
docker.networkis"none"(no egress). readOnlyRoot: trueblocks package installs.usermust be root forapt-get(omituseror setuser: "0:0"). OpenSoul auto-recreates containers whensetupCommand(or docker config) changes unless the container was recently used (within ~5 minutes). Hot containers log a warning with the exactopensoul sandbox recreate ...command.
{
agents: {
defaults: {
sandbox: {
mode: "non-main", // off | non-main | all
scope: "agent", // session | agent | shared (agent is default)
workspaceAccess: "none", // none | ro | rw
workspaceRoot: "~/.opensoul/sandboxes",
docker: {
image: "opensoul-sandbox:bookworm-slim",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp", "/var/tmp", "/run"],
network: "none",
user: "1000:1000",
capDrop: ["ALL"],
env: { LANG: "C.UTF-8" },
setupCommand: "apt-get update && apt-get install -y git curl jq",
pidsLimit: 256,
memory: "1g",
memorySwap: "2g",
cpus: 1,
ulimits: {
nofile: { soft: 1024, hard: 2048 },
nproc: 256,
},
seccompProfile: "/path/to/seccomp.json",
apparmorProfile: "opensoul-sandbox",
dns: ["1.1.1.1", "8.8.8.8"],
extraHosts: ["internal.service:10.0.0.5"],
},
prune: {
idleHours: 24, // 0 disables idle pruning
maxAgeDays: 7, // 0 disables max-age pruning
},
},
},
},
tools: {
sandbox: {
tools: {
allow: [
"exec",
"process",
"read",
"write",
"edit",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"session_status",
],
deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"],
},
},
},
}Hardening knobs live under agents.defaults.sandbox.docker: network, user, pidsLimit, memory, memorySwap, cpus, ulimits, seccompProfile, apparmorProfile, dns, extraHosts.
Multi-agent: override agents.defaults.sandbox.{docker,browser,prune}.* per agent via agents.list[].sandbox.{docker,browser,prune}.* (ignored when agents.defaults.sandbox.scope / agents.list[].sandbox.scope is "shared").
# Build the default sandbox image
scripts/sandbox-setup.shThis builds opensoul-sandbox:bookworm-slim using Dockerfile.sandbox.
# Sandbox common image (optional)
If you want a sandbox image with common build tooling (Node, Go, Rust, etc.), build the common image:
scripts/sandbox-common-setup.shThis builds opensoul-sandbox-common:bookworm-slim. To use it:
{
agents: {
defaults: {
sandbox: { docker: { image: "opensoul-sandbox-common:bookworm-slim" } },
},
},
}# Sandbox browser image
To run the browser tool inside the sandbox, build the browser image:
scripts/sandbox-browser-setup.shThis builds opensoul-sandbox-browser:bookworm-slim using Dockerfile.sandbox-browser. The container runs Chromium with CDP enabled and an optional noVNC observer (headful via Xvfb).
Notes:
- Headful (Xvfb) reduces bot blocking vs headless.
- Headless can still be used by setting
agents.defaults.sandbox.browser.headless=true. - No full desktop environment (GNOME) is needed; Xvfb provides the display.
Use config:
{
agents: {
defaults: {
sandbox: {
browser: { enabled: true },
},
},
},
}Custom browser image:
{
agents: {
defaults: {
sandbox: { browser: { image: "my-opensoul-browser" } },
},
},
}When enabled, the agent receives:
- a sandbox browser control URL (for the
browsertool) - a noVNC URL (if enabled and headless=false)
Remember: if you use an allowlist for tools, add browser (and remove it from deny) or the tool remains blocked. Prune rules (agents.defaults.sandbox.prune) apply to browser containers too.
# Custom sandbox image
Build your own image and point config to it:
docker build -t my-opensoul-sbx -f Dockerfile.sandbox .{
agents: {
defaults: {
sandbox: { docker: { image: "my-opensoul-sbx" } },
},
},
}# Tool policy (allow/deny)
denywins overallow.- If
allowis empty: all tools (except deny) are available. - If
allowis non-empty: only tools inalloware available (minus deny).
# Pruning strategy
Two knobs:
prune.idleHours: remove containers not used in X hours (0 = disable)prune.maxAgeDays: remove containers older than X days (0 = disable)
Example:
- Keep busy sessions but cap lifetime:
idleHours: 24,maxAgeDays: 7 - Never prune:
idleHours: 0,maxAgeDays: 0
# Security notes
- Hard wall only applies to tools (exec/read/write/edit/apply_patch).
- Host-only tools like browser/camera/canvas are blocked by default.
- Allowing
browserin sandbox breaks isolation (browser runs on host).
# Troubleshooting
- Image missing: build with
scripts/sandbox-setup.shor setagents.defaults.sandbox.docker.image. - Container not running: it will auto-create per session on demand.
- Permission errors in sandbox: set
docker.userto a UID:GID that matches your mounted workspace ownership (or chown the workspace folder). - Custom tools not found: OpenSoul runs commands with
sh -lc(login shell), which sources/etc/profileand may reset PATH. Setdocker.env.PATHto prepend your custom tool paths (e.g.,/custom/bin:/usr/local/share/npm-global/bin), or add a script under/etc/profile.d/in your Dockerfile.