Self-Hosting
This page is for running SmartChats yourself: on your laptop, a VPS, a Raspberry Pi, anywhere with a shell.
For most people, Quick Start already covers it. This page goes further: env-var management, running on a VPS, Docker, and what runs inside the stack.
TL;DR
curl -fsSL https://smartchats.ai/install | bash
smartchats setupThat gives you a self-hosted SmartChats on your machine. Everything below is for cases where you need more control.
What you need
- macOS (Apple Silicon) or Linux with a shell. Intel Macs use the npm package. Windows is experimental (see Quick Start).
- An OpenAI API key.
setuptreats it as the required key. It covers chat models, embeddings and OpenAI voices. - Optional: Anthropic, Google (Gemini), xAI, Together AI, Azure Speech and Serper keys for more models, the turn verdict, Azure voices and web search.
- 3 GB free disk.
setupanddoctorwarn below that.
The installer bundles the CLI, the server (both compiled with Bun, runtime included) and the SurrealDB binary. No Node, no Docker, no system-level deps required.
The stack, briefly
smartchats start launches two processes:
┌──────────────────────────────────────┐
│ smartchats-server (port 3000) │
│ │
│ • serves the SPA at / │
│ • handles /local-api/* (health, │
│ llm, tts, embeddings, data, │
│ usage, keys, tools, insights, │
│ storage) │
└─────────────────┬────────────────────┘
│ ws://127.0.0.1:8000/rpc
▼
┌──────────────────────────────────────┐
│ surreal (port 8000, loopback only) │
│ ~/.smartchats/data/surreal.db │
└──────────────────────────────────────┘
▲
│
browser at :3000The browser only needs port 3000. SurrealDB binds to 127.0.0.1. The server serves the static export of the Next.js app directly, so there is no Next.js runtime.
Where things live
| Path | Purpose |
|---|---|
~/.smartchats/bin/ | Installed binaries (smartchats, smartchats-server, surreal) |
~/.smartchats/app/out/ | The static SPA the server serves |
~/.smartchats/data/surreal.db | SurrealDB storage (rocksdb). Change the directory with start --data-dir |
~/.smartchats/run/pids.json | Live process IDs, ports and start times |
~/.smartchats/logs/ | Per-process logs (surreal.log, server.log) |
~/.smartchats/.env | API keys and config for a binary install. A source install uses <repo>/.env |
~/.smartchats/config.json | CLI preferences (last port, resolved source root). $XDG_CONFIG_HOME/smartchats/config.json if that variable is set |
smartchats home prints the install root (binary install) or the resolved source root.
Required environment
The stack boots without any keys, but the agent cannot reply until at least one LLM provider key is configured. smartchats start prints a NO API KEYS CONFIGURED box when it can’t find any.
| Variable | Required? | Effect if missing |
|---|---|---|
OPENAI_API_KEY | Strongly recommended | OpenAI models, embeddings and OpenAI voices unavailable |
ANTHROPIC_API_KEY | Optional | Claude models can’t be selected |
GOOGLE_API_KEY (or GEMINI_API_KEY) | Optional | Gemini models can’t be selected |
XAI_API_KEY | Optional | Grok models can’t be selected |
TOGETHER_API_KEY (or TOGETHERAI_API_KEY) | Recommended for voice | The default turn verdict is skipped and every voice turn is answered |
AZURE_SPEECH_KEY + AZURE_SPEECH_REGION | Recommended for voice | The default voice provider (Azure) fails. Pick an OpenAI voice instead |
SERPER_API_KEY | Optional | Agent’s web-search tool is a no-op |
SMARTCHATS_API_KEY | Optional | Bearer token for scripts and agents on other machines (Other devices and scripts) |
SMARTCHATS_AUTH | Optional | off lets every device in without pairing |
setup and env only prompt for OpenAI, Anthropic, Google and Serper. Add the others to the .env file by hand, then run smartchats restart.
Environment configuration
smartchats start reads the .env file at the install root (or repo root), merges in your shell environment, and passes the result to the server. A variable set in your shell wins over the same name in .env, so OPENAI_API_KEY=sk-... smartchats start overrides the file for one run.
Each provider has a SMARTCHATS_-prefixed name and one or more plain names. The server checks them in the order below and uses the first one set. If none is set, it falls back to a key stored in SurrealDB with POST /local-api/keys.
| Provider | Checked in this order |
|---|---|
| OpenAI | SMARTCHATS_OPENAI_API_KEY, OPENAI_API_KEY |
| Anthropic | SMARTCHATS_ANTHROPIC_API_KEY, ANTHROPIC_API_KEY |
| Google (Gemini) | SMARTCHATS_GOOGLE_API_KEY, GOOGLE_API_KEY, GEMINI_API_KEY |
| xAI | SMARTCHATS_XAI_API_KEY, XAI_API_KEY |
| Together AI | SMARTCHATS_TOGETHER_API_KEY, TOGETHER_API_KEY, TOGETHERAI_API_KEY |
| Azure Speech key | SMARTCHATS_AZURE_SPEECH_KEY, AZURE_SPEECH_KEY |
| Azure Speech region | SMARTCHATS_AZURE_SPEECH_REGION, AZURE_SPEECH_REGION |
| Serper (web search) | SMARTCHATS_SERPER_API_KEY, SERPER_API_KEY |
Azure Speech is read from the environment only. A database-stored Azure key is not used for voice.
Server settings
These are read by smartchats-server. smartchats start sets the SURREAL_*, SMARTCHATS_HOST, SMARTCHATS_PORT and SMARTCHATS_STATIC_DIR values itself, so setting them in .env has no effect under start. They matter when you run the server another way, such as smartchats dev.
| Variable | Server default | Set by start to |
|---|---|---|
SMARTCHATS_HOST | 127.0.0.1 | 0.0.0.0 |
SMARTCHATS_PORT | 4242 | --port (default 3000) |
SMARTCHATS_STATIC_DIR | unset (API only) | ~/.smartchats/app/out |
SURREAL_URL | ws://127.0.0.1:8000/rpc | ws://127.0.0.1:<surreal-port>/rpc |
SURREAL_NS | smartchats | smartchats |
SURREAL_DB | main | main |
SURREAL_USER | root | root |
SURREAL_PASSWORD | root | root |
Media storage. smartchats start runs SeaweedFS and sets these for the server, so you only set them to use your own S3-compatible bucket instead (all four are needed). See Storage for the bind address and the browser-facing endpoint:
| Variable | Default | Notes |
|---|---|---|
SMARTCHATS_S3_ENDPOINT | set by start | e.g. https://<account>.r2.cloudflarestorage.com |
SMARTCHATS_S3_ACCESS_KEY_ID | unset | |
SMARTCHATS_S3_SECRET_ACCESS_KEY | unset | |
SMARTCHATS_S3_BUCKET | unset | |
SMARTCHATS_S3_REGION | us-east-1 | Signing region |
SMARTCHATS_S3_LINKS | server | direct: browsers get presigned bucket URLs instead of links through the server (Storage) |
Where the .env file lives
| Install path | .env location |
|---|---|
curl | bash install | ~/.smartchats/.env |
| Source checkout, or npm install | <repo>/.env (npm installs clone to ~/.smartchats/cli/source) |
| Docker container | /root/.smartchats/.env (mount the host file there, see below) |
Ways to write keys:
smartchats env # interactive walkthrough (recommended)
smartchats env --list # show what's configured (masked)
vi ~/.smartchats/.env # direct edit, needed for xAI / Together / Azuresmartchats setup also writes the file on first run. After editing, run smartchats restart (or docker restart smartchats) to pick up the changes.
To check which keys the CLI sees, run:
smartchats env --list
# Output:
# ✓ OpenAI sk-V…vGiE (.env $OPENAI_API_KEY)
# ✗ Anthropic (Claude) not set (optional)Sharing one .env between bare-metal and Docker installs
The Docker container reads /root/.smartchats/.env (its install root), and the bare-metal install reads ~/.smartchats/.env on the host. Mounting the host file into the container reuses the same config:
docker run -d --name smartchats \
-p 3000:3000 \
-v ~/.smartchats/.env:/root/.smartchats/.env:ro \
-v ~/.smartchats/data:/data \
sheunaluko/smartchats:latestEdit ~/.smartchats/.env later, then:
docker restart smartchatsAdding keys after the stack is running
smartchats envon the host (recommended): interactive prompts, writes to~/.smartchats/.env. Thensmartchats restart, ordocker restart smartchatsif you mounted the file into the container.- Edit the file directly:
vi ~/.smartchats/.env, then restart. This is the only way to add xAI, Together and Azure keys. - Store a key in SurrealDB with the keys API. No restart needed, and an environment key for the same provider still wins:
curl -X POST http://localhost:3000/local-api/keys \
-H 'content-type: application/json' \
-d '{"keys":{"together":"..."}}'The API accepts openai, anthropic, google, xai, together and azure. GET /local-api/keys/providers shows which providers are available and where each key came from.
Other devices and scripts
The computer running SmartChats needs nothing. A phone or another computer pairs once: it shows Pair this device, and you enter the code from
smartchats pair
# Pairing code: 482 913 (valid 5 min, one device)or from Settings → Devices → Pair a new device. smartchats devices lists paired devices and smartchats devices revoke <id> removes one.
In Docker your own browser is another device to the container, so it pairs once too. While nothing is paired, the container prints a code at startup (docker logs smartchats, valid 15 minutes); for a new one run docker exec smartchats smartchats pair.
Scripts and agents on other machines can send Authorization: Bearer $SMARTCHATS_API_KEY instead, when you set that variable. SMARTCHATS_AUTH=off turns all of this off. Details: Security.
Running on a VPS
The same install and commands work on a VPS. For a public deploy, put a reverse proxy in front for TLS and access control.
Install on the VPS
ssh user@vps
curl -fsSL https://smartchats.ai/install | bashThe installer runs the interactive smartchats setup. For a scripted install, write ~/.smartchats/.env first and use the non-interactive form:
curl -fsSL https://smartchats.ai/install | bash -s -- --non-interactive
smartchats setup --no-promptRestrict access to port 3000
smartchats start always binds the server to 0.0.0.0, so port 3000 is reachable on every interface. There is no flag to change this. Block the port at the firewall and let only the reverse proxy reach it:
# ufw example: allow SSH and HTTPS, deny direct access to 3000
sudo ufw allow OpenSSH
sudo ufw allow 443/tcp
sudo ufw deny 3000/tcp
sudo ufw enableSurrealDB already binds to 127.0.0.1 only.
Front it with nginx + Let’s Encrypt
Keep Host $host and X-Forwarded-For in the config: they are how the server tells visitors from this machine (Security). Visitors then pair like any other device; auth_basic adds a second lock if you want one.
server {
listen 443 ssl http2;
server_name smartchats.your-domain.com;
ssl_certificate /etc/letsencrypt/live/.../fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/.../privkey.pem;
auth_basic "SmartChats";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Auto-start on boot (systemd)
Run the stack in the foreground so systemd supervises it. On SIGTERM, start --foreground stops both processes.
# /etc/systemd/system/smartchats.service
[Unit]
Description=SmartChats local stack
After=network.target
[Service]
Type=simple
User=YOUR_USER
WorkingDirectory=/home/YOUR_USER
ExecStart=/home/YOUR_USER/.smartchats/bin/smartchats start --foreground --no-prompt
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now smartchats
sudo systemctl status smartchatsPeriodic backup
#!/bin/bash
# /etc/cron.daily/smartchats-backup (runs as root, so use full paths)
mkdir -p /home/YOUR_USER/backups
/home/YOUR_USER/.smartchats/bin/smartchats data export \
/home/YOUR_USER/backups/$(date +%Y-%m-%d).json --target=localDocker (alternative)
There is no published image. Build one from Dockerfile.aio in a clone of the repo:
git clone https://github.com/sheunaluko/smartchats.git
cd smartchats
docker build -f Dockerfile.aio -t smartchats-aio:latest .Each release also publishes this image as sheunaluko/smartchats:<version> and :latest on Docker Hub, and as ghcr.io/sheunaluko/smartchats-aio on GitHub’s registry (linux/amd64 and linux/arm64), so building is optional. The Dockerfile runs curl … install | bash against debian:bookworm-slim, so the image contains a published release (pin one with --build-arg SMARTCHATS_VERSION=v0.4.0). Its entrypoint is smartchats start --data-dir=/data --foreground --no-prompt, and /data holds the database and media. Media storage runs inside the container and the browser reaches it through port 3000, so that is the only port to publish.
docker run -d --name smartchats \
-p 3000:3000 \
-v ~/.smartchats/.env:/root/.smartchats/.env:ro \
-v ~/.smartchats/data:/data \
sheunaluko/smartchats:latestThe .env mount is the same file the bare-metal CLI reads (~/.smartchats/.env), so one config works for both install modes. See Sharing one .env between bare-metal and Docker installs.
Don’t run the container and a native stack on the same data directory at once. Both store the database at <data-dir>/surreal.db, so ~/.smartchats/data works for either, one at a time.
-e flags also work for one-off runs:
docker run --rm -p 3000:3000 \
-e TOGETHER_API_KEY=$TOGETHER_API_KEY \
-v ~/.smartchats/data:/data \
sheunaluko/smartchats:latestbin/aio in a clone wraps the build and run steps (--rebuild, -d, --port, --data-dir, --tag). It names the container smartchats, which is the name smartchats doctor checks.
Docker is useful when you need process isolation or container orchestration. For laptop self-hosting, the native install is simpler.
Backup + restore
# Snapshot (writes a single JSON file)
smartchats data export ~/snapshot.json --target=local
# Wipe and restore (the import upserts every row by id)
smartchats stop
rm -rf ~/.smartchats/data
smartchats start
smartchats data import ~/snapshot.json --target=localThe fresh stack boots with an empty database, and the import writes the rows back. data --target=local always connects to ws://localhost:8000/rpc, so it needs the default --surreal-port.
Troubleshooting
Port 3000 already in use → another process is bound to it. Either stop it (lsof -i:3000 to find it), or smartchats start --port 3030.
smartchats start exits with “surreal binary not found” → in a binary install, ~/.smartchats/bin/surreal is missing and there is no surreal on PATH. Re-run the installer. In a source or npm install, install SurrealDB with the command start prints.
The agent doesn’t reply → no provider API keys reachable. Run smartchats env --list to see what the CLI found, add keys to .env, then smartchats restart.
Diagnosing a running stack → smartchats status for a health snapshot, smartchats logs -f to tail per-process output, smartchats logs --proc server -n 200 for just the server’s last 200 lines.
Iterating on the source? → smartchats dev (Next.js dev server with hot reload, the local server and SurrealDB in Docker) instead of smartchats start. See Contributing.
See also
- Quick Start: install and first run.
- CLI Reference: every command and flag.
- Architecture: the open / cloud boundary and what’s inside the stack.
- Contributing: modifying SmartChats itself.