Getting started#
Zero-to-first-session walkthrough. Plan for ~15 minutes if Postgres is already on the host; 25 minutes if you need to install one too.
This guide is intentionally end-to-end — it covers the things that sit around opendray (installing the CLIs it wraps, bootstrapping Postgres) on top of the deploy paths in the README. If you've used opendray before and just want to redeploy, the condensed paths in README Production deploy are a better fit.
Already know "is opendray for me?" If not, read the What is opendray? section in the README first. The bullets there will save you 15 minutes if your use case isn't a match.
Step 0 — what you'll need#
| Tool | Why | Note |
|---|---|---|
| At least one of: Claude Code / Codex CLI / Gemini CLI | opendray is a wrapper, not a model — it spawns a CLI on your host | Step 1 below |
| PostgreSQL 15 / 16 / 17 + pgvector extension | State, sessions, memory vectors | Step 2 below |
go 1.25+ and pnpm 10+ — only if you build from source |
Skip if you grab a release binary | Releases page |
A reachable network port (default :8770) for the web admin |
UI + API + WebSockets | Bind to 127.0.0.1 unless behind a reverse proxy |
Step 1 — install at least one AI CLI#
opendray spawns these CLIs against your local accounts. You install
them just like you would for terminal use; opendray finds them on
$PATH.
Claude Code (recommended starting point)#
[object Promise]After login, credentials sit at ~/.claude/credentials.json.
opendray reads them automatically when you select the claude
provider.
Codex CLI (OpenAI)#
[object Promise]Gemini CLI (Google)#
[object Promise]Verify at least one is reachable#
[object Promise]You can run opendray with just one CLI installed and add the others later. The provider list is dynamic — opendray probes the binary at spawn time, missing ones just show as "command not found" in the Sessions error panel.
Step 2 — install Postgres + pgvector#
opendray requires PostgreSQL 15, 16, or 17 with the
pgvector extension. Pick
the install method matching your host.
macOS (Homebrew)#
[object Promise]Ubuntu / Debian#
[object Promise]Other Linux#
Use your distro's PG packages, then either install pgvector via package or build from source.
Bootstrap the opendray database (one-time)#
In psql connected as a superuser:
CREATE EXTENSION vectorneeds superuser. After it lands,opendray_useronly needs the CRUD privileges granted above — opendray never reconnects as superuser at runtime.
Test the credentials from the host you'll run opendray on:
[object Promise]You should see check: ok and no errors.
Step 3 — pick a deploy path and install opendray#
Decision question first: are you here for the session spawn feature (drive Claude / Codex / Gemini from the web Sessions page)?
If YES — you need a "Full" path#
| Your host | Path | README section |
|---|---|---|
| macOS as 24/7 home server | macOS LaunchDaemon | Option D |
| Linux box / VPS / LXC | systemd | Option B |
| Just testing in foreground | go run from source |
Quickstart |
| Hand-rolled supervisor (s6 / runit / launchd Agent) | Direct binary | Option C |
Skip Docker. The image is distroless (no Node, no AI CLIs, no
pg_dump), so the Sessions tab will error on every spawn click. See the §A callout for the architectural reason.
If NO — you only need channels / integrations / notes / API#
| Your host | Path | README section |
|---|---|---|
You can still receive messages on Telegram / Slack / etc., write notes, hit the integration API, and view the web admin. You just can't spawn local AI CLI sessions from this deployment.
All paths converge on:
[object Promise]The minimum two fields in config.toml:
Everything else has sensible defaults — see config.example.toml
inline comments for the full surface.
Step 4 — first login + change admin password#
Open http://localhost:8770/admin/ (or whatever host:port opendray
is bound to via listen in config.toml).
- Log in as
admin+ the password you put in[admin].password. - Immediately go to Settings → Admin → Change password.
Why immediately: after the first password change, opendray writes a
bcrypt-hashed keyfile at $HOME/.opendray/secrets/admin.key and the
plaintext [admin].password in config.toml becomes inert (the
keyfile takes precedence). Until you change it, your only protection
is filesystem permissions on config.toml.
The full credential precedence chain is in operator-guide §admin.
Step 5 — configure a Provider#
Providers → click the provider you installed in Step 1 → fill in:
- Command path — absolute path to the CLI binary
(
which claudefinds it; on Apple Silicon Homebrew installs land at/opt/homebrew/bin/claude). - Accounts dir (Claude only, optional) — a directory of named
Claude credential sets if you want to switch identities per
session. Leave blank to use the default
~/.claude.
Save. opendray runs a one-off <cli> --version to probe; the
provider card turns green when the binary is reachable.
Step 6 — spawn your first session#
Sessions → New session → pick the provider → pick a working directory (any project on your machine) → Spawn.
A browser-side terminal opens. Type prompts as you would in a real terminal. Close the tab and the session keeps running on the host; come back, the scrollback is intact.
Step 7 (optional) — add a Telegram channel#
This is the feature that makes opendray different from
tmux + ssh. With a channel wired up, opendray pushes
notifications when a session goes idle (CLI is waiting on input),
and your reply on Telegram flows back as the next stdin write.
One-time Telegram setup#
- Telegram → search @BotFather → start chat.
/newbot→ BotFather walks you through name + username → issues a token like123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11.- Find your chat ID:
- DM your bot once (any text).
- Open
https://api.telegram.org/bot<token>/getUpdates— your numericchat.idis in the JSON response.
In opendray#
Channels → New channel → kind Telegram:
- Bot token: from BotFather
- Default chat ID: the
chat.idfromgetUpdates - Notify on: tick
session.idle(or all three topics)
Save → click Test on the channel card. Within seconds you'll see a test message in Telegram.
Now leave a session idle for 30 seconds (the default idle threshold
— configurable via [session].idle_threshold). Telegram pings you
with the last bit of CLI output. Reply, and the text flows back
into the session's stdin.
What next?#
- More channels: Slack / Discord / Feishu (飞书) / DingTalk
(钉钉) / WeCom (企业微信) — each has its own setup in the in-app
Tutorial at
/admin/tutorial/. - API integrations: docs/integration-guide.md — scoped API keys, reverse-proxy mount, events WebSocket.
- Memory subsystem: enable local-first embeddings via
[memory.backend] = "local"or wire up Ollama / LM Studio — see in-app Tutorial → Memory section. - Encrypted backups: configure
[backup]to push DB dumps to S3 / R2 / B2 / SFTP / rclone — see operator-guide §backup.
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
relation "providers" does not exist on migrate |
Pre-v2.0.0 binary (issue #162) | Pull the latest binary — fix is in v2.0.0 |
type "vector" does not exist on migrate |
pgvector extension not enabled in the opendray database | Run CREATE EXTENSION vector; as superuser in opendray |
Spawn session failed: executable file not found in $PATH |
The wrapped CLI isn't installed on the opendray host, or the Command Path in the Provider config is wrong | Step 1 above; verify with which claude (or whichever CLI) |
| Telegram bot doesn't respond to replies | Bot privacy mode is on by default (bot only sees commands) | BotFather → /setprivacy → Disable |
Bad gateway through a reverse proxy |
Proxy isn't forwarding WebSocket upgrade headers | See operator-guide §Topology for nginx / Caddy snippets |
| Sessions tab is empty but Channels work | Likely the binary can spawn but no Provider configured | Step 5 |
See also#
- README — install table, deploy paths, project status
- README.zh.md — Simplified Chinese version
- docs/quickstart.md — 5-minute dev environment (more focused than this guide)
- docs/operator-guide.md — operator reference: topology, auth, backup, logging
- docs/integration-guide.md — third-party API surface
- VERSIONING.md — major-as-generation versioning
- CHANGELOG.md — release history