Self-Hosting
Requirements
- Ruby 3.4.6 through mise or another Ruby manager
- SQLite for local development
- PostgreSQL for production
- Docker for container builds and runner execution
- Kamal for production deploys
Local Setup
mise exec -- bundle install
mise exec -- bin/rails db:setup
mise exec -- bin/dev
The default seeded account is admin@xmode.local with password password123 unless ADMIN_EMAIL and ADMIN_PASSWORD are set.
Demo data is seeded by default for bender.demo@xmode.local with password password123. Set DEMO_PLANET_EXPRESS=0 to disable it, or override DEMO_BENDER_EMAIL and DEMO_BENDER_PASSWORD.
Planet Express demo workspaces use a fake agent simulator for provider and local-shell actions. The dashboard includes a demo operation form that creates an issue, runs the Implement Issue pipeline, and writes mock logs and artifacts without calling an external agent.
Codex/OpenAI actions use a deterministic provider adapter by default. To call the OpenAI Responses API for an action, set OPENAI_API_KEY in the process environment and configure the action runtime with "mode": "live" and an optional "model" value. Do not store API keys in action runtime config. Live provider output is still validated against the action output schema and recorded as run messages and artifacts.
Local open-source model actions use the stable local_model provider and can target an Ollama-compatible backend with provider: ollama when the runtime should be explicit. Configure:
LOCAL_MODEL_RUNTIME, defaultollamaLOCAL_MODEL_BASE_URL, defaulthttp://xmode-ollama:11434LOCAL_MODEL_NAME, defaultqwen2.5:0.5bLOCAL_MODEL_TIMEOUT_SECONDS, default120
Set the action runtime to "mode": "live" to call the private model service. Without live mode, or when the service is unavailable, xmode records deterministic fallback output so normal pipelines do not fail because a local model is stopped. On small CPU-only hosts, use this for planning, classification, follow-ups, and sandbox-adjacent summaries; keep code-changing work behind sandbox evidence and Change Requests.
GitHub should be connected through a GitHub App when possible. Self-hosted workspaces can create a private GitHub App from Settings -> Integrations -> Create GitHub App; xmode sends GitHub a manifest with the repository permissions it needs, stores the returned private key in the encrypted integration secret, and then guides the user to install the app on selected repositories.
For hosted or centrally managed deployments, create the app in GitHub, set the setup URL to:
https://your-app-host.example.com/integrations/github_app_callback
Grant repository metadata and contents access for repository import and branch pushes, plus pull request write access for Change Request creation. Then configure the xmode process with:
XMODE_GITHUB_APP_IDorGITHUB_APP_IDXMODE_GITHUB_APP_SLUGorGITHUB_APP_SLUGXMODE_GITHUB_APP_PRIVATE_KEYorGITHUB_APP_PRIVATE_KEYXMODE_GITHUB_APP_PRIVATE_KEY_PATHorGITHUB_APP_PRIVATE_KEY_PATH
The private key can be passed as raw PEM or with escaped newlines. Centrally configured GitHub App private keys are not stored in the database. Workspace installation ids are stored on integration accounts, and xmode exchanges short-lived installation tokens when it imports repositories or creates Pull Requests.
GitLab tokens and manual fallback GitHub tokens are stored with Active Record encryption. Configure these through credentials or environment variables before saving real provider tokens:
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEYACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEYACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
When a code-changing run has a repository connection backed by a GitHub App installation or GitLab/manual provider token, xmode commits sandbox changes to a new branch, pushes it, and creates the provider Pull Request or Merge Request. Without provider credentials, xmode records the local Change Request shell and marks provider creation as missing token.
Applications can send bugs, warnings, and operational signals into the Event Inbox through the Node.js, Python, and Ruby event SDKs at https://github.com/m9rc1n/xmode-events. Configure the app URL, workspace slug, source, and workspace webhook secret from Settings -> Integrations -> Signed webhook intake.
Sandbox Execution
Code-changing runs execute through one seam, Sandboxes::Engine, which selects an adapter from the execution environment's runner_mode:
local_worktree-> host process (developer machines)docker-> a resource-bounded, hardeneddocker runon the local socketcloud_worker(the default) -> host process, unless a sandbox engine is configured, in which case it runs in a real container
This is deliberately gated: with no engine configured, cloud_worker runs the host process (unchanged), so dev and CI are unaffected. Enable real containers in production by setting one of:
XMODE_SANDBOX_ENGINE=docker— containerizecloud_workeron the local docker socketXMODE_SANDBOX_DOCKER_HOST=...— point the sandbox engine at a dedicated docker host
Tuning and hardening (optional; the engine falls back to the CODEX_DOCKER_* values when unset):
XMODE_SANDBOX_STORAGE_VOLUME— required in containerized prod; mounts the run worktree via the named volume (xmode_storage). A sibling container spawned from the job container cannot bind-mount a path that only exists inside the job container's volume, so the named volume is the only mount the host daemon can resolve.XMODE_SANDBOX_CPUS/XMODE_SANDBOX_MEMORY/XMODE_SANDBOX_PIDS/XMODE_SANDBOX_TMPFS/XMODE_SANDBOX_NETWORK— resource and egress limits.XMODE_SANDBOX_READONLY_ROOT=1— opt-in read-only root filesystem (breaks workloads that write outside the workspace or/tmp).
Containers always run with --cap-drop ALL and --security-opt no-new-privileges, and the docker socket is never mounted into a sandbox container.
Persistent project machines keep a warm per-project checkout and branch a fresh git worktree per run instead of cloning every time. Enable with XMODE_PROJECT_MACHINES=1.
Sandbox sessions expire (expires_at) and are reaped hourly by SandboxReaperJob: expired sessions are destroyed, any container/volume removed, and the worktree checkout reclaimed (run artifacts are preserved). Codex Cloud tasks are polled to completion by CodexCloudPollJob and a recurring sweep, streaming the transcript back into the session and capturing the diff for review.
See docs/sandbox-architecture.md for the full design.
Production Shape
xmode is designed to run as:
- web process: Rails/Puma/Thruster
- job process: Solid Queue workers
- Postgres accessory
- optional Ollama accessory for private local model experiments
- persistent
/rails/storagevolume for run artifacts
Kamal config lives in config/deploy.yml.