Skip to main content

Deployment Overview

CubePlex can be self-hosted on your own infrastructure. Both deployment modes below run the same backend and frontend container images — only the orchestration differs.

Choose a deployment target​

Docker ComposeKubernetes (Helm)
Best forA single host — quick self-hosted setup, small teams, internal demosMulti-node clusters, production-scale, autoscaling
Orchestrationdocker compose up -dhelm upgrade --install
Infra includedPostgres, Redis, rustfs, OpenSandbox, TempoPostgres, Redis, rustfs, Tempo, and the alibaba OpenSandbox umbrella
GuideDocker Compose install guideKubernetes install guide

If you're not sure, start with Docker Compose — it's the simpler setup and covers everything except horizontal scaling across multiple machines.

Agent sandbox​

CubePlex runs agent tool calls (bash, file read/write, …) inside an OpenSandbox sandbox. It is a required part of every deployment: Docker Compose includes the service, and Helm bundles it by default. Helm may instead point at an externally managed OpenSandbox endpoint, but a backend always requires a non-empty endpoint, sandbox image, and API key. Sandbox images default to Docker Hub (opensandbox/*) and GHCR (ghcr.io/cubeplexai/cubeplex-sandbox); mainland-China mirrors are noted inline in each guide.

LLM provider configuration​

Both deployment modes configure LLM providers the same way, as a block under llm in the backend's secret configuration. This reference applies whether you're editing config.production.secrets.yaml (Docker Compose) or values.local.yaml (Kubernetes) — each guide links back here instead of repeating it.

The most portable way to configure a provider is to point at any OpenAI-compatible (api: openai-completions) or Anthropic-compatible (api: anthropic-messages) endpoint. This covers OpenAI, Anthropic, Azure OpenAI, most cloud vendors, and self-hosted gateways (vLLM, LiteLLM, Ollama, …) — you supply base_url, api_key, and the models the endpoint exposes.

llm:
# A default preset is REQUIRED. The backend seeds it from model_presets at
# startup and resolves every chat message through it — without one the backend
# still boots but chat fails at runtime with NoDefaultPresetError (HTTP 500).
# Each tier's primary / fallbacks is "<provider_name>/<model_id>"; the
# provider_name must appear under providers below.
model_presets:
tiers:
lite: { enabled: true, primary: "openai/gpt-5.6-terra", fallbacks: ["anthropic/claude-opus-4.8"] }
flash: { enabled: true, primary: "openai/gpt-5.6-terra", fallbacks: ["anthropic/claude-opus-4.8"] }
pro: { enabled: true, primary: "openai/gpt-5.6-terra", fallbacks: ["anthropic/claude-opus-4.8"] }
max: { enabled: false, primary: null, fallbacks: [] }
default_preset: pro
providers:
# Any OpenAI-compatible chat-completions endpoint.
openai:
base_url: "https://api.openai.com/v1" # includes /v1
api_key: "sk-..."
api: "openai-completions"
models:
- id: "gpt-5.6-terra"
name: "GPT-5.6 Terra"
input: ["text", "image"]
context_window: 128000
max_tokens: 16384

# Any Anthropic-compatible Messages endpoint.
anthropic:
base_url: "https://api.anthropic.com" # host root, no /v1
api_key: "sk-ant-..."
api: "anthropic-messages"
models:
- id: "claude-opus-4.8"
name: "Claude Opus 4.8"
reasoning: true
input: ["text", "image"]
context_window: 200000
max_tokens: 64000
  • model_presets.tiers defines the selectable model tiers (lite / flash / pro / max); default_preset picks which tier serves requests that don't ask for a specific one. At least one tier must be enabled. Each primary / fallbacks entry is "<provider_name>/<model_id>"; the provider_name must appear under providers, and fallbacks are tried in order if primary fails. Tiers you don't need can stay enabled: false.
  • Each provider declares base_url, api_key, api (openai-completions | anthropic-messages | openai-responses), and at least one entry in models. base_url follows each SDK's convention — OpenAI-style includes /v1, Anthropic-style is the host root.
  • Set reasoning: true only for reasoning models; input lists the modalities the model accepts (text, image).

Minimal viable configuration (one provider, one model):

llm:
model_presets:
tiers:
pro: { enabled: true, primary: "openai/gpt-5.6-terra", fallbacks: [] }
default_preset: pro
providers:
openai:
base_url: "https://api.openai.com/v1"
api_key: "sk-..."
api: "openai-completions"
models:
- id: "gpt-5.6-terra"
name: "GPT-5.6 Terra"
input: ["text", "image"]
context_window: 128000
max_tokens: 16384

Shortcut: built-in vendor presets​

For a known vendor you can skip base_url / api / models and reference a built-in preset instead — it fills in the endpoint and model list for you:

llm:
model_presets:
tiers:
pro: { enabled: true, primary: "deepseek/deepseek-v4-flash", fallbacks: [] }
default_preset: pro
providers:
deepseek:
preset: "deepseek/cn/anthropic-messages"
api_key: "sk-..."

Preset keys are vendor/region/protocol[/plan] and live in backend/cubeplex/llm/catalog/data/vendors.yaml (deepseek / aliyun / volcengine / moonshot / zhipu / minimax / openrouter / anthropic / openai, and more).

Required secrets​

Every deployment needs three auth secrets, regardless of mode:

SecretPurposeGenerate with
jwt_secretSigns and verifies user session JWTsopenssl rand -hex 32
csrf_secretCSRF double-submit cookieopenssl rand -hex 32
vault_keyFernet key encrypting the MCP / credentials vaultpython -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'

All three are required. The backend refuses to start outside tests if either signing secret is empty, fewer than 32 characters, or a known placeholder such as REPLACE_ME, USE ENV, or CHANGE_ME…. Helm also rejects those placeholders during installation.

Upgrading artifact object keys​

Releases that switch artifact storage from artifacts/{conversation_id}/{artifact_id}/... to artifacts/{artifact_id}/... keep reading the owner-conversation legacy key while migration is in progress. Use this rollout order:

  1. Deploy the new backend so both canonical and legacy reads are available.
  2. Run python scripts/dev/migrate_artifact_object_keys.py inside a backend container. The command is idempotent and retains legacy objects by default.
  3. Verify artifact download, preview, sharing, and skill publication.
  4. Optionally rerun the command with --delete-source to remove copied legacy objects.

Objects historically written below a conversation other than the artifact's owner are not discovered automatically and require manual recovery.

Metrics​

The backend serves Prometheus text at GET /metrics without application authentication. Scrape each backend instance separately. The endpoint has no tenant, user, conversation, run, model, or raw tool names in its labels. Counters reset when a backend process restarts; billing records remain the source of truth for historical usage.

MetricMeaning
cubeplex_http_requests_totalCompleted HTTP requests by route template, method, and status. SSE requests count when the stream closes.
cubeplex_http_request_duration_secondsCompleted non-SSE HTTP request duration. Health probes and /metrics are excluded.
cubeplex_http_requests_in_flightHTTP requests currently being served, including SSE streams.
cubeplex_sse_connections_active, cubeplex_sse_connections_opened_totalOpen SSE streams and streams opened since process start.
cubeplex_agent_run_attempts_active, cubeplex_agent_run_attempts_started_totalWorker tasks executing or cleaning up, and tasks scheduled by trigger and phase. A HITL pause has no active worker.
cubeplex_agent_run_attempt_duration_secondsWorker task duration including cleanup; time waiting for a HITL answer is excluded.
cubeplex_agent_runs_finished_totalDurable terminal outcomes recorded by this process: completed, cancelled, errored, failed.
cubeplex_agent_hitl_pauses_totalRun attempts that reached a HITL pause.
cubeplex_llm_calls_total, cubeplex_llm_tokens_totalCommitted LLM billing events and their token usage. Call outcomes are success and fallback_failed.
cubeplex_tool_calls_totalCompleted tool calls by fixed category and success/error outcome.

The Prometheus Python client also exports process_cpu_seconds_total, process_resident_memory_bytes, and process_open_fds. Use host/container exporters for machine and container resources, and separate Postgres/Redis exporters for those services.

For scrape setup, see the Docker Compose and Kubernetes guides.

Next steps​