Deployment

Openheim ships as a single binary with four subcommands. This guide covers running the server mode (openheim serve) in home-cloud and enterprise environments.


Subcommands

Subcommand Description
openheim Interactive TUI (no subcommand)
openheim acp ACP agent over stdin/stdout — for IDE extensions (Zed, Claude Code, etc.)
openheim run "<prompt>" One-shot headless prompt, output to stdout
openheim serve WebSocket + REST server
openheim init Create a default ~/.openheim/config.toml

Server mode is the primary deployment target for hosted and enterprise use.


Interactive TUI

Running openheim with no subcommand opens the interactive terminal UI. Type a message and press Enter to chat. Type :help to see all commands.

Command Description
:help Show all commands
:q / :quit Exit
:new Start a new session (keeps the current model)
:sessions Browse and restore saved sessions (interactive picker)
:config Show current configuration
:models Open model picker (arrow keys + Enter to switch mid-session)
:models <name> Switch to a model directly without opening the picker
:mcp Show MCP server statuses
:skills List available skills
:theme Open theme color picker
:theme <name> Apply a theme color directly and save it to config.toml

Keyboard shortcuts: ↑/↓ scroll · PgUp/PgDn page · Ctrl+C cancel the running turn · Ctrl+C twice (within two seconds) quit · Esc close any overlay. Pasted text keeps its line breaks (shown as ↵ in the input bar) and is sent only when you press Enter.

Theme colors: white, gray, blue, cyan, magenta, green, yellow, red, pink

The selected theme is persisted to ~/.openheim/config.toml as theme_color under [tui].


Running the server

# Default: binds 0.0.0.0:1217
openheim serve

# Custom host/port
openheim serve --host 127.0.0.1 --port 8080

The server exposes:

  • ws://{host}:{port}/ws — WebSocket endpoint (ACP agent + filesystem sidecar)
  • http://{host}:{port}/api/* — REST endpoints (config, models, skills, tools, mcp-servers, sessions)

The filesystem sidecar is sandboxed to the configured work_dir (see Configuration) — connected clients cannot read or write outside it.


Configuration

Openheim reads ~/.openheim/config.toml at startup. In server deployments, place the config at the path the process user can read. See Configuration for the full reference.

API keys via environment variables

Provider API keys can be supplied as environment variables instead of being stored in the config file:

Environment variable Provider
ANTHROPIC_API_KEY Anthropic
OPENAI_API_KEY OpenAI
GEMINI_API_KEY Google Gemini

Any provider block in config.toml with env_var = "MY_VAR" reads from that variable at startup. This is the recommended approach for production — store secrets in your secret manager and inject them at runtime.

[providers.anthropic]
api_base = "https://api.anthropic.com/v1"
default_model = "claude-sonnet-4-6"
models = ["claude-sonnet-4-6", "claude-opus-4-7"]
env_var = "ANTHROPIC_API_KEY"

Logging

Openheim uses tracing with RUST_LOG for log level control:

RUST_LOG=info openheim serve
RUST_LOG=openheim=debug openheim serve   # debug only openheim internals

Logs go to stderr, except in the TUI: once it's on screen, they're appended to ~/.openheim/openheim.log instead.


Docker

The repo ships a multi-stage Dockerfile and a docker-compose.yml. The image runs as a non-root openheim user, and docker-entrypoint.sh generates a default config (openheim init) on first start if no config volume is mounted.

# Build and start with docker compose
docker compose up --build

# Or run manually
docker build -t openheim .
docker run -p 1217:1217 \
  -e OPENAI_API_KEY=sk-your-key \
  -v openheim-data:/home/openheim/.openheim \
  openheim serve

Dockerfile (as shipped)

FROM rust:1.92-slim-bookworm AS builder
WORKDIR /workspace
RUN apt-get update && apt-get install -y pkg-config libssl-dev \
    && rm -rf /var/lib/apt/lists/*
COPY Cargo.toml Cargo.lock* ./
COPY src ./src
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates libssl3 curl \
    && rm -rf /var/lib/apt/lists/*
COPY --from=builder /workspace/target/release/openheim /usr/local/bin/openheim
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh \
    && useradd -m -u 1000 openheim
USER openheim
EXPOSE 1217
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["openheim", "serve"]

docker-compose.yml (as shipped)

services:
  openheim:
    build: .
    container_name: openheim-agent
    ports:
      - "1217:1217"
    environment:
      - RUST_LOG=info
      # API keys are read from config.toml, but can also be set here
      # and referenced via env_var in your config
      - OPENAI_API_KEY=${OPENAI_API_KEY:-}
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
    restart: unless-stopped
    volumes:
      # Config directory — mount your own config.toml here;
      # a default config is generated on first run if not provided
      - openheim-config:/home/openheim/.openheim
      # Persistent workspace directory
      - openheim-workspace:/workspace

volumes:
  openheim-config:
    driver: local
  openheim-workspace:
    driver: local
# Start
ANTHROPIC_API_KEY=sk-ant-... docker compose up -d

# View logs
docker compose logs -f openheim

systemd

Create /etc/systemd/system/openheim.service:

[Unit]
Description=Openheim LLM agent server
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=openheim
Group=openheim
ExecStart=/usr/local/bin/openheim serve --host 127.0.0.1 --port 1217
Restart=on-failure
RestartSec=5s

# Secrets — set these in /etc/openheim/env or use a secret manager
EnvironmentFile=/etc/openheim/env

# Harden the process
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/home/openheim/.openheim

[Install]
WantedBy=multi-user.target

/etc/openheim/env:

ANTHROPIC_API_KEY=sk-ant-...
RUST_LOG=warn
# Create the service user
useradd --system --home /home/openheim --create-home openheim

# Copy config
mkdir -p /home/openheim/.openheim
cp config.toml /home/openheim/.openheim/
chown -R openheim:openheim /home/openheim/.openheim

# Enable and start
systemctl daemon-reload
systemctl enable openheim
systemctl start openheim
systemctl status openheim

Reverse proxy

The WebSocket endpoint requires a proxy that supports connection upgrades. Both nginx and Caddy handle this without extra plugins.

nginx

server {
    listen 443 ssl;
    server_name openheim.example.com;

    ssl_certificate     /etc/letsencrypt/live/openheim.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/openheim.example.com/privkey.pem;

    location / {
        proxy_pass         http://127.0.0.1:1217;
        proxy_http_version 1.1;

        # WebSocket upgrade headers
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Keep long-running agent sessions alive
        proxy_read_timeout  3600s;
        proxy_send_timeout  3600s;
    }
}

Caddy

openheim.example.com {
    reverse_proxy localhost:1217
}

Caddy handles TLS and WebSocket upgrades automatically.


Enterprise considerations

Authentication

Openheim does not implement authentication itself. Add it at the reverse proxy layer:

  • Basic auth (simple deployments): nginx auth_basic directive
  • OAuth2 / OIDC: oauth2-proxy in front of nginx/Caddy
  • mTLS: configure client certificate verification in your proxy for service-to-service use

Network isolation

For environments where the agent should not reach arbitrary external hosts:

  • Run in a network namespace or Docker network with explicit egress rules
  • Allow only the LLM provider API endpoints and any MCP server URLs
  • The agent calls outbound to: the configured LLM provider API, and any MCP server URLs defined in config

Multi-tenancy

Openheim is designed as a single-user or small-team server. For multi-tenant deployments, run a separate instance per tenant (separate config, history directory, and API key) behind a routing layer. Sharing a single instance across tenants is not supported — conversation history and skills are not access-controlled at the application level.

Data residency

Conversation history is written to ~/.openheim/history/ as a pair of files per conversation: {uuid}.json (metadata, rewritten wholesale on each change) and {uuid}.jsonl (messages, appended one per line as the conversation grows). Back up the whole directory, not just *.json — the message content lives in the .jsonl files. For enterprises with strict data residency requirements, use a provider endpoint that runs in your region (Anthropic Bedrock, Azure OpenAI, self-hosted Ollama, etc.) and point openheim at it via an openai_compatible provider entry.