Docs / Getting Started / Getting started
Documentation

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.

7 min read Updated May 22, 2026 Source Edit History

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.

[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:

[object Promise]

CREATE EXTENSION vector needs superuser. After it lands, opendray_user only 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:

[object Promise]

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).

  1. Log in as admin + the password you put in [admin].password.
  2. 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 claude finds 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#

  1. Telegram → search @BotFather → start chat.
  2. /newbot → BotFather walks you through name + username → issues a token like 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11.
  3. Find your chat ID:
    • DM your bot once (any text).
    • Open https://api.telegram.org/bot<token>/getUpdates — your numeric chat.id is in the JSON response.

In opendray#

Channels → New channel → kind Telegram:

  • Bot token: from BotFather
  • Default chat ID: the chat.id from getUpdates
  • 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#