Skip to content

ENV vars

xixo reads its configuration from the environment. This page lists every variable read by the web and worker processes, the Rake tasks, and the development tooling. Each section covers one of these contexts:

Context Reads
Server The web and worker processes of the ghcr.io/xixo/xixo image, and any other way of running xixo. Nearly everything on this page.
Rake task arguments Arguments to bin/rails inference:corpus.
Development ./dev, the compose files, the seeds, and the test suite. A deployed server does not read them.

./dev reference fails when the code reads a variable this page does not list, or this page lists one the code no longer reads.

Set these the same on the web and worker containers, and restart both after changing one. Values are strings. A switch is on for 1, true, yes, or on, and off for 0, false, no, off, or nothing, in any case. xixo refuses to boot when a switch holds any other value.

A production deployment sets all of these. See self-hosting for how to generate them.

Variable Default
SECRET_KEY_BASE none Signs and encrypts sessions and cookies. Read by Rails. xixo ships no credentials file, so without it Rails raises Missing secret_key_base in production.
ENCRYPTION_PRIMARY_KEY none Encrypts the columns that hold secrets: each resource’s credentials, and each tenant’s masks client secret and registration access token. bin/rails db:encryption:init prints a value. Without it, the first read or write of one of those columns raises ActiveRecord::Encryption::Errors::Configuration.
ENCRYPTION_DETERMINISTIC_KEY none Encrypts columns that are looked up by value. No column uses it yet, and Rails requires it to be set.
ENCRYPTION_KEY_DERIVATION_SALT none Derives the per-column keys from the two above.
XIXO_PUBLIC_ORIGIN none The origin browsers and agents use to reach each tenant, with %{subdomain} standing in for the first label of the host, such as https://%{subdomain}.xixo.example.com. A single tenant uses an origin without the placeholder, such as https://xixo.example.com. The origin followed by /mcp is the audience every access token must name, and the sign-in callback is built from it. Production refuses to boot without it. In development and test, xixo builds both from the request’s Host header when it is unset.
MASKS_ISSUER_TEMPLATE none The issuer URL of the masks tenant each xixo tenant signs in through, with %{subdomain} standing in as it does in XIXO_PUBLIC_ORIGIN, such as https://%{subdomain}.auth.example.com. With XIXO_TENANT, %{subdomain} in either template is the pinned tenant, whatever host a request names. Unset, sign-in and the /mcp metadata fail with MASKS_ISSUER_TEMPLATE is not set. See signing in.
Variable Default
XIXO_TENANT none Declares a single tenant, such as xixo, and serves it at every host.
XIXO_TENANTS none Declares several tenants, separated by commas or spaces, such as demo,acme. A request resolves to the tenant named by the first label of its host.
XIXO_HOST_SUFFIX none, or xixo.test in development The domain xixo answers under, such as xixo.example.com. Rails accepts that domain and its subdomains and answers 403 to any other Host, except at /up. Unset in production, Rails accepts every host. xixo also refuses an MCP resource whose URL points at a host under this domain, so it cannot call itself.

bin/rails xixo:tenants creates each declared tenant that does not exist, and the container entrypoint runs it before the server starts. Setting both variables stops that task with Tenant::TenancyConflict, and the container exits. Setting neither declares no tenants. A request resolves against the tenants in the database, so removing a name from XIXO_TENANTS neither deletes that tenant nor stops xixo from serving it. See tenants.

xixo uses four PostgreSQL databases: the primary, a cache, a job queue, and Action Cable. Production connects to xixo_production, xixo_production_cache, xixo_production_queue, and xixo_production_cable as the role xixo. That role must hold neither SUPERUSER nor BYPASSRLS, because tenant isolation depends on row-level security. xixo refuses to enter any tenant when it connects as such a role.

Variable Default
POSTGRES_HOST localhost The PostgreSQL server’s host name.
POSTGRES_PORT 5432 The PostgreSQL server’s port.
XIXO_DATABASE_PASSWORD none The password of the xixo role in production. Unset, xixo connects without a password.
POSTGRES_USER xixo The role in development and test. Production ignores it and connects as xixo.
POSTGRES_PASSWORD xixo The password in development and test. Production ignores it.
PG_BIN_PATH none A directory put first on PATH, so db:schema:dump finds a pg_dump that matches the server’s PostgreSQL version when it writes db/structure.sql.
Variable Default
OPENSEARCH_URL http://127.0.0.1:9201 The OpenSearch server that holds the search index. The index’s alias is xixo_ followed by the Rails environment, and each tenant searches through an alias of its own. See search.
XIXO_EMBEDDING_DIMENSIONS 768 The length of the vectors the index holds. The embedding model a tenant declares must return vectors of this length, and checking an inference resource fails when it does not. Changing it changes the index’s mapping, so RebuildSearchIndexJob builds a new index at 3 a.m. See rebuilding.
XIXO_SEMANTIC_FLOOR 0.55 The lowest cosine similarity a semantic match may have. A match must also score within 0.12 of the best match. Raising it returns fewer, closer matches. See semantic search.

Uploads are staged through Active Storage before a job places them in a resource. See staging.

Variable Default
XIXO_STAGING_SERVICE local Where production stages uploads. local writes to /rails/storage, which the web and worker containers must share. s3 writes to the bucket named below. Any other name fails when Active Storage loads. Development always uses local.
XIXO_STAGING_BUCKET none The staging bucket, when XIXO_STAGING_SERVICE=s3.
XIXO_STAGING_ENDPOINT none The endpoint of an S3-compatible service, such as https://s3.example.com. Setting it addresses the bucket by path. Unset, xixo uses AWS.
XIXO_STAGING_REGION us-east-1 The staging bucket’s region.
XIXO_STAGING_ACCESS_KEY_ID none
XIXO_STAGING_SECRET_ACCESS_KEY none
XIXO_FILESYSTEM_ROOTS none Directories filesystem resources may use, separated by colons. Each tenant is confined to <root>/<tenant> under each root, and a relative root in a resource resolves under the first one. The files resource that config/resources.yml gives every tenant as default storage is one of these. Unset, every filesystem resource fails its check with no filesystem roots are permitted.
XIXO_GIT_ROOT none The directory where git resources keep their shallow bare clones, at <root>/<tenant id>/<resource id>.git. Unset, every git resource is unusable.

xixo refuses to connect to loopback, private, link-local, and other reserved addresses. These variables open exceptions. Each _ORIGINS variable lists origins separated by commas, such as http://minio:9000,https://search.internal.example.com. An origin is a scheme, a host, and a port, and a URL matches when all three match. See addresses and security.

Variable Default
XIXO_S3_ORIGINS none S3 endpoints an s3 resource may reach at a private address, such as a MinIO container.
XIXO_MCP_ORIGINS none MCP servers an mcp resource may reach at a private address.
XIXO_SEARCH_ORIGINS none Search endpoints a search resource may reach at a private address, such as a self-hosted SearXNG. XIXO_ALLOW_PRIVATE_FETCH does not apply to search resources.
XIXO_INFERENCE_ORIGINS none Every origin an openai_compatible resource may reach, public or private, such as https://api.openai.com,http://ollama:11434. A resource whose base URL is outside the list is unusable, and with the list empty every one is, unless it is reached through a transport.
XIXO_TAILSCALE_SOCKET none The socket of a tailscaled that xixo shares a network with, such as /var/run/tailscale/tailscaled.sock. Set, xixo declares a tailnet resource in every tenant beside those in config/resources.yml, and resources reached through it may connect to addresses on the tailnet. See reach a tailnet.
XIXO_ALLOW_PRIVATE_FETCH false Lifts the address check for downloads, snapshots, and every resource that fetches URLs. The scheme check stays.
XIXO_GIT_PROTOCOLS https The transports git resources may use, separated by commas, such as https,ssh. xixo passes the list to git as GIT_ALLOW_PROTOCOL. Only http and https URLs go through the address check.
Variable Default
XIXO_WHISPER_MODEL none The path to a whisper.cpp ggml model file, such as /models/whisper/ggml-base.en.bin. The image carries whisper-cli and no model. Unset, audio and video are analyzed without a transcript, and the analysis logs no transcription model is configured.
XIXO_WHISPER_BIN whisper-cli The whisper.cpp command, found on PATH unless it is a path.
XIXO_TRANSCRIBE_SECONDS 3600 How many seconds from the start of a recording are measured and transcribed. Whisper runs at about real time on a CPU.
XIXO_CHROME_PATH detected The Chrome or Chromium binary used to render snapshots. Unset, xixo looks in the usual places for the platform. The image includes Chromium. Without a browser, web resources report themselves unusable.
XIXO_CHROME_NO_SANDBOX false Starts Chrome with --no-sandbox, for a container that cannot give Chrome the namespaces its sandbox needs.
Variable Default
XIXO_MCP_LIMIT 120 MCP requests per minute, counted per tenant and per bearer token, or per IP address for a request without one. A request over the limit receives a 429. 0 refuses every request.
XIXO_RUN_BUDGET 20 Syncs, exports, and analyses each subject may start over MCP per clock hour. 0 removes the limit. See budgets and gates.
XIXO_RUN_DEADLINE_HOURS 6 How long a run or an analysis may stay open. Every 10 minutes, ExpireOverdueJob expires any that are past their deadline. 0 sets no deadline.
XIXO_RUN_RETENTION_DAYS 14 How long finished runs are kept. SweepRunsJob deletes older ones at 4:20 a.m. 0 keeps them.
XIXO_AUDIT_RETENTION_DAYS 90 How long audit events are kept. SweepAuditEventsJob deletes older ones at 4 a.m. 0 keeps them.

./bin/jobs runs Solid Queue with two worker pools, set in config/queue.yml. The first takes the sync, export, and default queues with 5 threads per process. The second takes the analysis queue.

Variable Default
BULK_CONCURRENCY 1 Processes for the sync, export, and default queues.
ANALYSIS_CONCURRENCY 1 Processes for the analysis queue.
ANALYSIS_THREADS 4 Threads per analysis process.
ANALYSIS_PER_TENANT 2 Analyses one tenant may run at the same time, across every process, so one tenant cannot fill the pool.
SOLID_QUEUE_IN_PUMA false Runs the job supervisor inside the web server, for a small host with no worker container.
XIXO_ITERATORS_DISABLED false Closes every gate in every tenant, which stops every sync, analysis, export, and reindex, including ones in progress. See budgets and gates.

The image runs ./bin/thrust ./bin/rails server. Thruster listens on HTTP_PORT and forwards to Puma on TARGET_PORT.

Variable Default
RAILS_MAX_THREADS 3 Puma threads per process. Each database connection pool holds this many connections, or enough for an analysis process’s threads, ANALYSIS_THREADS + 5, whichever is more, so setting it for Puma never starves the worker.
WEB_CONCURRENCY 0 Puma worker processes. 0 runs one process without forking. Read by Puma.
PORT 3000 The port Puma listens on. Thruster sets it to TARGET_PORT.
PIDFILE none Where Puma writes its process ID.
HTTP_PORT 80 The port Thruster listens on. Read by Thruster.
TARGET_PORT 3000 The port Thruster forwards to. Read by Thruster.
TLS_DOMAIN none Has Thruster obtain a certificate from Let’s Encrypt for this domain and serve HTTPS itself. Read by Thruster, which reads more variables than are listed here.
Variable Default
RAILS_ENV production in the image production, development, or test.
RAILS_LOG_LEVEL info debug, info, warn, error, or fatal. Production only.
RAILS_ASSUME_SSL true Treats every request as HTTPS, for a server behind a proxy that terminates TLS. Production only.
RAILS_FORCE_SSL true Redirects HTTP to HTTPS and sends Strict-Transport-Security. Production only.
SECRET_KEY_BASE_DUMMY unset Boots with a throwaway secret, for build steps such as assets:precompile. Read by Rails. A server running with it set is not secure.

bin/rails inference:corpus analyzes the files in test/fixtures/corpus against a tenant’s default inference resource and prints each summary.

Variable Default
TENANT demo The subdomain of the tenant to run in. The task stops when the tenant does not exist or has no default inference resource.
ONLY none Analyzes only the corpus files whose path contains this text.

Read by ./dev, the compose files, the seeds, and the test suite. A deployed server ignores them.

./dev and the compose files read these from your shell. Compose also passes your shell’s XIXO_TENANT to the containers, defaulting to xixo, and compose.multi.yml clears it and sets XIXO_TENANTS=demo,acme.

Variable Default
XIXO_PORT 8180 The host port of the app, at http://xixo.localhost:8180.
XIXO_DOCS_PORT 8181 The host port of the docs site.
XIXO_VITE_PORT 8182 The host port of the Vite dev server.
XIXO_POSTGRES_PORT 5434 The host port of PostgreSQL.
XIXO_OPENSEARCH_PORT 9201 The host port of OpenSearch.
XIXO_MINIO_PORT 9000 The host port of MinIO’s S3 API.
XIXO_MINIO_CONSOLE_PORT 9001 The host port of MinIO’s console.
XIXO_MCP_OAUTH_PORT 8190 The host port of the test MCP server that signs in with OAuth, at http://mcp.localhost:8190. Compose lists it in XIXO_MCP_ORIGINS.
XIXO_MCP_OAUTH_LIFETIME 120 How many seconds the test MCP server’s access tokens last.
XIXO_OLLAMA_PORT 11434 The port of Ollama on the host. Compose points OLLAMA_URL and XIXO_INFERENCE_ORIGINS at it.
XIXO_WHISPER_DIR ~/.cache/whisper The host directory mounted read-only at /models/whisper.
XIXO_WHISPER_FILE ggml-base.en.bin The model file in that directory. Compose sets XIXO_WHISPER_MODEL to it.
MASKS_ISSUER http://masks.localhost:12345 The masks issuer. Compose sets MASKS_ISSUER_TEMPLATE from it, and compose.multi.yml defaults it to http://%{subdomain}.masks.localhost:12345.
MASKS_CLIENT_DIR ../masks/client The masks client checkout that compose builds and mounts into the containers.
MASKS_CLIENT_PATH none Where the Gemfile finds the masks client. When that directory holds a masks.gemspec, the gem comes from it. Otherwise the gem comes from RubyGems, or from ../masks/client under Gemfile.local. Compose sets it to /masks/client as a build argument.
MASKS_CONTAINER masks-masks-1 The masks container that ./dev delegation runs its setup in.
MASKS_TENANT masks The masks tenant that ./dev delegation signs in through.
DEV_ALLOWED_HOSTS none Hosts the Vite and Astro dev servers answer, separated by commas. Set by compose.
DEV_CLIENT_PORT none The port the browser uses to reach the Vite or Astro dev server for live reload. Set by compose.
VITE_RUBY_HOST localhost Where Rails finds the Vite dev server. Read by vite_ruby, and set by compose.
VITE_RUBY_SKIP_PROXY false Read by vite_ruby, and set by compose.
DOCS_SITE https://xixo.pages.dev The docs site’s published URL, for canonical links and the sitemap. The docs workflow sets it for preview deploys.

bin/rails db:seed gives each tenant an s3 resource when S3_ENDPOINT is set, and an openai_compatible resource when OLLAMA_URL is set.

Variable Default
S3_ENDPOINT none The endpoint of the seeded s3 resource. Set, the seeds attach it as items- and the tenant’s subdomain. Compose sets it to http://minio:9000.
S3_REGION us-east-1 Its region.
S3_ACCESS_KEY_ID items Its access key. Compose sets it to xixo.
S3_SECRET_ACCESS_KEY xixoxixo Its secret key.
OLLAMA_URL none The base URL of an OpenAI-compatible server, such as http://host.docker.internal:11434/v1. Set, the seeds attach it as ollama and make it the default inference resource.
OLLAMA_FAST_MODEL gemma3:4b The model for the fast role.
OLLAMA_SMART_MODEL llama3.1:8b The model for the smart role.
OLLAMA_VISION_MODEL gemma3:4b The model for the vision role.
OLLAMA_AGENT_MODEL qwen3:8b The model for the agent role.
OLLAMA_EMBEDDING_MODEL nomic-embed-text The model for the embedding role. Its vectors must have XIXO_EMBEDDING_DIMENSIONS dimensions.
Variable Default
XIXO_TEST_SEARCH_ENGINE none The URL of a real OpenSearch server for the tests. Unset, the tests use an in-process fake and skip the ones that assert what OpenSearch itself does.
TEST_ENV_NUMBER none Added to the search index’s alias, such as xixo_test_2, so parallel test workers do not share an index. The test suite sets it for each worker.
CI unset Eager loads the application in tests, as production does.