Installation
OtoDock is self-hosted: you run it on your own server. This guide gets a working instance up and serving its dashboard with one short install script. When you're done, head to First run to create your account and chat with your first agent. (Developing OtoDock? See From source at the end.)
Prerequisites
- A server: Linux (Debian 12 or Ubuntu 22.04 LTS, or newer), or a Mac with Apple silicon, such as a Mac mini, running Docker Desktop. 4 GB RAM is the minimum, and more is recommended for multi-user installs and parallel agent sessions, especially if your agents use the heavier Docker MCP sidecars (the Camoufox browser, file tools). See How much RAM? below for what fits.
- An x86-64 or arm64 CPU. The release images ship for both, and the installer refuses an unsupported CPU with a clear message.
- Docker and the Docker Compose plugin. If the server doesn't have them yet, install Docker first.
That's all. The OtoDock image ships with everything else baked in (the AI CLIs, the sandbox runtime, the toolchain agents use).
Install
Create a folder for the install, then run the script in it:
mkdir otodock && cd otodock
curl -fsSLO https://raw.githubusercontent.com/OtoDock/oto-dock/main/scripts/install.sh
bash install.sh
The installer checks Docker, writes a .env with a generated database password, handles the
Ubuntu 24.04+ host step when the host needs it, offers a small swap tuning (these two steps
may ask for sudo, and declining the tuning is safe), downloads the release-pinned
docker-compose.yml and the phone-service overlay, and starts the stack. Everything lands in
the folder you run it from, and it performs fresh installs only: it stops rather than
touch an existing install.
That one .env file is the whole configuration: on first boot OtoDock generates its
remaining secrets and appends them there, your data lives in named Docker volumes, and the
compose file pins the release it shipped with.
Upgrading later is one script, described in Upgrading & backups. Every optional knob is documented in the Configuration reference.
When it finishes, skip ahead to Verify it's running. If your users
will browse to the server by name or IP rather than localhost, set the public URL in the
generated .env (its comments show where):
# .env
DASHBOARD_PUBLIC_URL=http://your-server:8400 # the address your users browse to
TZ=Europe/Athens # optional: scheduled tasks and
# notification times use this
…then apply it:
docker compose up -d
If your team signs in through an identity provider (Authentik, Keycloak, Entra ID, …), add
the OIDC_* settings to the same .env.
Users & Access shows the exact
lines. You can also do this later, since local accounts work out of the box.
The phone service
The standard install includes the phone service, the voice engine behind live
voice conversations in the dashboard and real
phone calls through Twilio or your Asterisk/FreePBX phone system.
There's nothing to enable: connect speech providers under Setup → Audio and voice is
ready. If you don't want it, remove docker-compose.phone.yml from the COMPOSE_FILE
line in .env.
For phone calls through a local PBX, one extra setting applies:
OTO_AUDIOSOCKET_PUBLIC_HOST, the address on your local network where the PBX reaches the
phone service. That's the Docker host's LAN IP, because the PBX link belongs on your LAN or
a private tunnel, never the open internet. With it set, the phone service listens on that
address only, on port 9092 for call audio and 9093 for caller details. Until it's
set, only the server itself can reach these ports. Twilio needs no extra ports at all. See
Phone Calls for the full setup.
How much RAM?
OtoDock admits agent sessions based on live free memory, so a full box politely asks you to retry instead of crashing, but the ceiling is set by your RAM. Here is a rough guide for concurrent Claude Code / Codex CLI sessions. Each grows to roughly 1 GB with a full context, and the same numbers apply to Docker Compose and source installs:
| Server RAM | Concurrent sessions | …alongside a heavy Docker MCP (e.g. the browser) |
|---|---|---|
| 4 GB | ~3 | ~2 |
| 8 GB | ~5 to 6 | ~4 to 5 |
| 16 GB | ~10 to 12 | ~9 to 11 |
| 32 GB | 20+ | 19+ |
Add roughly 0.5 to 1 GB on top for the phone service, which the standard install includes.
Verify it's running
OtoDock serves both its API and dashboard on port 8400 by default. The first start takes a few minutes: the images are pulled, then the server builds its tool environments before it answers.
curl http://localhost:8400/health # → ok
Then open http://<your-server>:8400 in a browser. A fresh install greets you with the
setup wizard. Continue to First run.
Day-to-day, two commands cover operations (run them next to the compose file):
docker compose logs -f otodock-proxy # follow the server logs
docker compose down # stop the stack (your data stays in its volumes)
Reverse proxy & HTTPS
For anything beyond a local trial, put port 8400 behind a reverse proxy (Caddy, nginx, Traefik) or a tunnel that terminates TLS, and set up the proxy to:
- Keep the
Hostheader unchanged, and sendX-Forwarded-Protofrom the hop that terminates TLS. If the proxy rewritesHost, the dashboard's live connection is refused unlessDASHBOARD_PUBLIC_URLnames the address people use. - Add the visitor's address to
X-Forwarded-Forinstead of passing on one the visitor sent. On nginx, that'sproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;. - Pass WebSocket upgrades (the
UpgradeandConnectionheaders), since the live dashboard runs on/ws/.
Then tell OtoDock about the proxy in .env with two settings: DASHBOARD_PUBLIC_URL,
the address people use, and TRUSTED_PROXY, the address your proxy connects from.
If the proxy runs on another machine (a separate nginx or Nginx Proxy Manager box, a firewall appliance), trust that machine's LAN IP:
# .env
DASHBOARD_PUBLIC_URL=https://otodock.example.com
TRUSTED_PROXY=192.168.1.20 # the proxy machine's LAN IP
If the proxy runs on this server, installed directly (nginx, Caddy, cloudflared),
let only this server reach port 8400 and trust Docker's gateway, which is the address
OtoDock sees for a program on the same server:
# .env
DASHBOARD_PUBLIC_URL=https://otodock.example.com
PROXY_BIND_IP=127.0.0.1 # port 8400 answers only this server, so nobody skips the proxy
TRUSTED_PROXY=10.200.0.1 # Docker's gateway
Apply the changes with docker compose up -d, then docker compose restart otodock-proxy so OtoDock reads TRUSTED_PROXY again. If the value is wrong, OtoDock
shows the address your proxy actually connects from in a notice under Setup →
Security, ready to copy into TRUSTED_PROXY.
DASHBOARD_PUBLIC_URL drives login cookies, OAuth redirects, and links in notifications,
and with it set, in-chat artifacts and apps stay correctly sandboxed on that address even
if a hop drops X-Forwarded-Proto. TRUSTED_PROXY (plain IPs or CIDRs, comma-separate
several) lets OtoDock see each visitor's real address through the proxy, which the login
protections rely on. Until it's right, every visitor shares the proxy's address for the
rate limits, and accounts restricted to local-network sign-in can't sign in through the
proxy at all.
If you put OtoDock behind a gateway that forces a login on every request (Authentik, Authelia, oauth2-proxy, Cloudflare Access), a few paths must skip that login. The clients calling them authenticate themselves and can't complete a browser login:
^/ui-kit/: the styling and chart libraries that in-chat artifacts and apps load. These render in a hard security sandbox that deliberately sends no cookies, so a forced-login gateway silently blocks them and agent-built apps come out unstyled. The files are public static assets (fonts, CSS, chart libraries), so they are safe to exempt.^/s/: external share links (OtoDock 1.7 and later). When you share a chat or an app by a public link, the people who open it have no account, so they can't sign in to your gateway. The link has protection of its own: a random token, a password by default, a cookie bound to that one link, and an admin switch that turns links off. The app inside a link renders in the same cookieless sandbox as the artifacts above, so without this line even a signed-in person sees the link's page unstyled and stuck on "reconnecting".^/v1/apps/[0-9a-f-]{36}/(api|ws|platform|client)(/|$): apps with a server (1.7 and later). An app's page calls its own server and loads its own files from that same cookieless sandbox, and OtoDock checks every call with a short-lived viewer token it issues itself. Without this line the app renders but never connects, or shows no styles.^/v1/apps/[0-9a-f-]{36}/inbound/: an app's incoming webhooks (1.7 and later). A service such as Stripe or GitHub posts its events straight to the app's own address, with no browser session, and OtoDock verifies each event with the app's stored secret before anything runs.^/v1/webhooks/: every incoming trigger fire and every integration event (the vendor webhook receivers). OtoDock authenticates these itself with keys and signed requests.^/v1/satellite: only if you use Remote Machines. The satellite authenticates with its own pairing credentials./ws/: the live dashboard connection. A WebSocket can't follow a login redirect, so exempt it too. OtoDock checks your signed-in session on the socket itself.^/v1/images/temp/: only if you use reverse image search. The search vendor fetches the photo through a short-lived, random tokenized URL and has no browser session.^/v1/twilio/: only if you connect the phone service to Twilio. The call webhook, live call audio, and call-status callbacks all arrive from Twilio's cloud, which has no browser session. OtoDock checks Twilio's signature on every one of these requests.
Everything else, including the dashboard and all its settings, stays behind your gateway. The API & Webhooks page shows where these exemptions go in each common gateway, and how to check them from outside.
OtoDock pins its own internal Docker subnets, so no host Docker daemon changes are needed. If
one of them overlaps your LAN or VPN, override it in .env: OTODOCK_NETWORK_SUBNET
(10.200.0.0/24), OTODOCK_MCP_ADDRESS_POOL (10.201.0.0/16), and
OTODOCK_SOCKETPROXY_SUBNET, OTODOCK_DATA_SUBNET and OTODOCK_INTERNAL_SUBNET
(10.202.0.0/24, 10.203.0.0/24 and 10.204.0.0/24).
From source (developers & contributors)
Everything above runs the published images. If you're developing OtoDock, clone the repository and use the source flow.
Containerised, built from source
It needs only Git and Docker, because everything builds inside the containers:
git clone https://github.com/OtoDock/oto-dock.git
cd oto-dock
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" > config.env
scripts/compose.sh up -d --build
scripts/compose.sh stacks the build overlay (docker-compose.build.yml) on the same base
compose file, with the base images pinned from VERSIONS.md. It builds exactly what your
working tree contains, so updating is just git pull and re-running the same command.
The source flow keeps its settings in config.env at the repository root (same contents and
behaviour as .env above).
Native (bare-metal development)
The server can also run directly on the host. One idempotent bootstrap script stands up the
whole environment: the pinned toolchain from VERSIONS.md (Python, Node, uv, pnpm), the
sandbox runtime, Docker, the server's virtualenv and config.env, PostgreSQL in a container
(loopback-only), and a dashboard build:
git clone https://github.com/OtoDock/oto-dock.git
cd oto-dock
scripts/dev-setup.sh
Then run it in the foreground while you work on the code, so the logs and restarts stay in your terminal:
cd proxy && venv/bin/python app.py # the server (serves the built dashboard too)
cd dashboard && npm run dev # optional second terminal: hot-reload dashboard
If the box doubles as a long-running instance, register it as a systemd service instead. The bootstrap generates the unit for your checkout path and user, enables it, and starts it:
scripts/dev-setup.sh --service
Updating a source install is covered in Upgrading.
Per-agent storage quotas enforce limits as warnings on any setup, but hard enforcement (blocking writes past a cap) needs a native install on an XFS volume with project quotas. A Docker Compose deployment always uses the warning-only tier. See Usage & limits.
Next steps
- First run →: create your administrator account and meet your pre-installed assistant.
- Connecting your AI →: plug in the Claude, ChatGPT, or other models your agents will run on.
- Upgrading & backups →: keep your instance current and your data safe.