Skip to Content
DocumentationReferenceEnvironment variables

Environment variables

Environment variables configure the Hub stack. The desktop app and cihub generate most of them, and most are not meant to be edited by hand. For changes you make from the dashboard, see custom settings. For the environment-variable workflow, see custom environment variables.

The variables below are internal to the Hub stack. Editing them incorrectly can break your installation. Stop the stack before editing, and start it again with cihub up prod --detached.

Where the values come from

Hub reads each setting from the first of these places that has a value:

  1. The Hub container’s environment. Docker Compose builds it from the stack’s env file and from values the compose file sets itself.
  2. state/settings.json. The Settings page writes this file. See Folder structure.
  3. The built-in default.

Hub writes the values it settled on to state/.env.resolved each time it starts. One variable reverses the order: for ALLOW_ERROR_MONITORING, the Settings switch wins over the variable, so a variable can never turn crash reporting back on for someone who turned it off.

The stack’s env file is .env.dev on macOS and Linux and .env on Windows, in the data root, with an identical copy under the other name. See Folder structure.

The desktop app rewrites the env file each time it launches. It keeps the values it manages — ROOT_FOLDER_HOST, the generated secrets, DOMAIN once your CI Account has set it, COMPOSE_PROFILES, the Sentry addresses, TAILSCALE_AUTHKEY, and TAILSCALE_SERVE_USER_DISABLED — and derives the rest from the app itself. A line you add by hand is not one of those values, so the app drops it on its next launch. Put a preference on the Settings page when one exists.

Core

VariableDescriptionDefault
CI_HUB_VERSIONHub version. The desktop app writes it from the pinned Hub image on every launch — do not set it.(from the image)
CI_HUB_ENVIRONMENTRuntime environment: production, staging, or development. It also selects the default CI Cloud address.production
INTERNAL_IPThe address Hub tells apps to listen on (APP_HOSTNAME) and builds local links from. The desktop app writes 0.0.0.0.127.0.0.1
ARCHITECTUREHost CPU architecture: amd64 or arm64. Hub reads it from the host probe and falls back to the container’s own.(detected)
ROOT_FOLDER_HOSTAbsolute path of the data root on the host. Docker uses it for bind mounts. Windows drive-letter paths are accepted.(required)
JWT_SECRETSecret Hub uses to sign tokens. The desktop app generates it once and keeps it.(required)
NODE_ENVNode.js environment: production, development, or staging.production
LOG_LEVELLog verbosity: debug, info, warn, or error.info
TZTime zone passed to apps that respect it. An invalid value is replaced by the host’s zone.(host time zone, else UTC)
DEVICE_IDStable identifier for this machine, read from the host (for example /etc/machine-id). Hub uses it when it pairs with your CI Account. Do not copy it to another machine: Hub refuses to pair under a copied ID.(from the host)
CI_HUB_DATA_DIRData root for the cihub CLI when it is not run from a Hub checkout.(platform default)

Docker and stack

VariableDescriptionDefault
DOCKER_SOCKET_PATHHost path to the Docker socket that Hub mounts to manage app containers. The desktop app works it out on every launch and rewrites this line in the env file, so a value you add there does not last. To use a different engine, set CI_HUB_DOCKER_HOST or DOCKER_HOST in the environment of the process that starts Hub.(detected)
DOCKER_GIDGroup ID of the host docker group, so the Hub container can read the mounted socket. Detected automatically at startup.(auto-detected)
CI_HUB_CONTAINER_UID, CI_HUB_CONTAINER_GIDUser and group the Hub container runs as. The desktop app writes your own user and group IDs, or 0 on Windows and where Docker Desktop’s socket is root-only.(derived)
CI_HUB_IMAGEThe Hub image the stack runs. The updater pins it to a release.ghcr.io/companionintelligence/ci-hub:latest
COMPOSE_PROFILESCompose profiles to start. Hub adds private-vpn and cloudflare itself when they apply.(derived)
COMPOSE_FILE_HOSTHost path of the compose file the stack runs from.(the data root’s docker-compose.prod.yml)
DOCKER_PLATFORMPlatform the Hub image is pulled for: linux/amd64 or linux/arm64.(host architecture)

Hub resolves the socket automatically: it honours CI_HUB_DOCKER_HOST or DOCKER_HOST, then the engine that already holds the Hub containers, then the engines it finds on the machine. If that picks the wrong one, set CI_HUB_DOCKER_HOST when you start Hub — see Install Docker.

Networking

VariableDescriptionDefault
DOMAINPublic domain zone for exposed apps. Your CI Account sets it when you pair, and the desktop app keeps that value.companionintelligence.com
LOCAL_DOMAINBase of the internal hostnames that carry Cloudflare tunnel traffic to Traefik for apps you expose. It isn’t the address you open a local app at. When unset, Hub falls back to DOMAIN, then to localhost. See Finding Your App’s URL.(falls back to DOMAIN, then localhost)
HTTP_PORTHost port Traefik listens on for HTTP traffic.80, or 8880 in the Linux desktop app
HTTPS_PORTHost port Traefik listens on for HTTPS traffic.443, or 8443 in the Linux desktop app
TRAEFIK_DASHBOARD_PORTHost port mapped to Traefik’s port 8080. The shipped configuration does not serve Traefik’s dashboard without authentication.8080
API_PORTHost port the Hub API and dashboard are published on.5002
ACME_EMAILEmail address for Let’s Encrypt certificate notifications.admin@localhost
CI_CLOUD_URLAddress of your CI Account backend. Hub uses it for device registration, the app store, and sync.https://hub.ci.computer
HUB_TRUST_PROXYOverrides which proxy hops Hub trusts for a visitor’s real address: a hop count such as 1, or a list of addresses and subnets. Leave it unset unless you run your own proxy in front of Hub. A value that is too broad lets a spoofed X-Forwarded-For header look internal.(Hub works it out)
CI_HUB_EXTRA_CORS_ORIGINSExtra browser origins allowed to call the Hub API with credentials, as a comma-separated list of exact origins.(none)
LOCALDevelopment only. Makes Hub pick docker-compose.local.yml when no compose file is mounted into the container.false

Linux lets only root open ports below 1024, so the Linux desktop app publishes Traefik on 8880 and 8443. cihub up on rootful Docker uses 80 and 443 when they are free and otherwise moves to 8880 and 8443; under rootless Docker it always uses those two. Apps you expose with Public Web reach Traefik inside Docker, so they don’t depend on these host ports.

Database (PostgreSQL)

VariableDescriptionDefault
POSTGRES_HOSTDatabase hostname inside the Docker network.ci-hub-db
POSTGRES_DBNAMEDatabase name.companiondb
POSTGRES_USERNAMEDatabase user.companion
POSTGRES_PASSWORDDatabase password. The desktop app generates one.(required)
POSTGRES_PORTHost port for the database, published on 127.0.0.1 only. Hub always reaches it at ci-hub-db:6543 inside Docker.6543

Message queue (RabbitMQ)

VariableDescriptionDefault
RABBITMQ_HOSTRabbitMQ hostname inside the Docker network. The name ci-os-hub-queue still resolves as an alias.ci-hub-queue
RABBITMQ_USERNAMERabbitMQ user.companion
RABBITMQ_PASSWORDRabbitMQ password. A production Hub refuses to start without one and warns if it is the old default, admin. The desktop app generates one.(required)
RABBITMQ_PORTHost port for RabbitMQ, published on 127.0.0.1 only. Hub always reaches the broker at ci-hub-queue:5672 inside Docker.5001
QUEUE_TIMEOUT_IN_MINUTESMinutes after which Hub treats a queued action as stale. Installs and updates wait at least 45 minutes for image pulls regardless of this value.5

Inference

VariableDescriptionDefault
OLLAMA_URLURL Hub uses to reach a running Ollama instance.http://host.docker.internal:11434
VLLM_URLURL for an NVIDIA vLLM server. Settings → AI overrides it.http://host.docker.internal:8000
VLLM_API_KEYAPI key Hub sends to the vLLM server. Settings → AI overrides it.(none)
OMLX_URLURL for an Apple Silicon oMLX server. Settings → AI overrides it. Unset, Hub probes http://host.docker.internal:8000 on Apple Silicon only; a URL you set is probed on any platform.(none)
OMLX_API_KEYAPI key Hub sends to the oMLX server, if you started it with one.(none)
LEMONADE_URLURL for the Lemonade server on AMD GPU hosts.http://host.docker.internal:13305
LEMONADE_API_KEYAPI key Hub sends to Lemonade, if you started Lemonade with one. Hub sends it only to the configured Lemonade address. The compose file doesn’t set it, so put it in the env file of a Hub you start with cihub up; the desktop app rewrites its env file on every launch.(none)

VLLM_URL defaults to port 8000, and Hub probes oMLX on the same port, but only on Apple Silicon unless you set OMLX_URL. The two engines are not offered on the same machine. MTPLX_URL, DSPARK_URL, SPECULATIVE_INFERENCE_URL, LLAMACPP_URL, and LMSTUDIO_URL are no longer read. A decode endpoint and an encode endpoint in Settings override the selected engine.

The compose file sets OLLAMA_URL directly, so a value in the env file does not override it. When that address does not answer, Hub also tries http://172.17.0.1:11434 and http://localhost:11434.

Tailscale (private VPN)

The hub-tailscale sidecar runs only when either TAILSCALE_AUTHKEY is set or the sidecar already holds a Tailscale login. A Hub you start with cihub also keeps it off when PRIVATE_VPN_USER_DISABLED is true.

VariableDescriptionDefault
TAILSCALE_AUTHKEYPre-authentication key from tailscale.com  for unattended login. Optional; interactive setups can sign in from Hub instead. The desktop app keeps it when it rewrites the env file.(optional)
HUB_TAILSCALE_EXTRA_ARGSOverrides the arguments passed to tailscale up. The default accepts routes and advertises 172.18.0.0/16 and 172.19.0.0/16.(optional)
PRIVATE_VPN_USER_DISABLEDSet to true to keep cihub up from starting the hub-tailscale sidecar. Only cihub reads it: the desktop app ignores it and removes it from the env file. Hub still publishes through Tailscale on the host.false
TAILSCALE_SERVE_USER_DISABLEDSet to true to stop Hub from writing Tailscale Serve configuration. Hub then publishes neither its dashboard nor any Private VPN app, removes nothing it published before, and Private VPN stops appearing as an access option. The desktop app keeps it across launches.false
TAILSCALE_OAUTH_CLIENT_IDTailscale OAuth client ID with the devices:core:read scope. Lets this Hub list your tailnet so it can discover other Hubs to pair with in Hub Pool. Discovery only — a Hub without it can still be paired with and still serves pooled requests.(optional)
TAILSCALE_OAUTH_CLIENT_SECRETSecret for the OAuth client above. Both variables must be set for discovery to work.(optional)
HUB_POOL_USER_DISABLEDSet to true to opt this Hub out of Hub Pool: apps stay on local engines and paired Hubs route around it. Existing pairings are kept. Follows the same convention as PRIVATE_VPN_USER_DISABLED.false
HUB_POOL_OUTBOUND_DISABLED, HUB_POOL_INBOUND_DISABLEDSet either to true to turn off one direction of pooling: sending this Hub’s work to paired Hubs (outbound), or serving work for them (inbound). The Settings toggle cannot override them.false
Settings, Network tab: a Private VPN (Tailscale) card marked Inactive with the daemon state NeedsLogin and a Log In with Tailscale button, above a Hub Pool card marked Local only, with a note that Tailscale is not connected, pooling controls, and a Save buttonSettings, Network tab: a Private VPN (Tailscale) card marked Inactive with the daemon state NeedsLogin and a Log In with Tailscale button, above a Hub Pool card marked Local only, with a note that Tailscale is not connected, pooling controls, and a Save button
Settings → Network shows the state these variables control. The Private VPN card reads Inactive until Tailscale signs in, and the Hub Pool card below it is the feature HUB_POOL_USER_DISABLED opts out of.

Hub Pool tuning

These variables tune how Hub Pool places requests and how long it waits for an answer. The defaults suit most pairs of Hubs, so leave them alone until Recent routing gives you a reason to change one. Put them in the stack’s env file (see Where the values come from) and restart the stack. The desktop app drops lines it doesn’t manage the next time it launches, so they last only on a Hub you start with cihub up. The three kill switches, HUB_POOL_USER_DISABLED, HUB_POOL_OUTBOUND_DISABLED, and HUB_POOL_INBOUND_DISABLED, are in Tailscale (private VPN) above.

VariableDescriptionDefault
HUB_POOL_MAX_PROMPT_TOKENSThe largest chat or completion prompt this Hub should serve for the pool while another Hub can take it. Hub estimates a prompt’s size as the request body in bytes divided by four. A longer prompt goes to another Hub when one can serve it, and still runs here when none can. A whole number from 1024 to 1048576; any other value is ignored. It overrides the ceiling you set with cihub pool ceiling.(no ceiling)
HUB_POOL_THROUGHPUT_PLACEMENTSet to off, 0, or false to stop measured response speed from reordering which Hub is tried first. Hub keeps measuring, so turning it back on needs no warm-up.on
HUB_POOL_CONTENTION_PLACEMENTSet to off, 0, or false to stop this Hub’s own engine from giving way to paired Hubs when it is busy with work a new request can’t join.on
HUB_POOL_SLOWER_PLACEMENT_RATIOHow many times slower the Hub that would be tried first must be predicted to be before a faster Hub goes ahead of it. A number of 1 or more; anything else reads as the default. A very large value, such as 1000, turns the rule off.3
HUB_POOL_SLOWER_PLACEMENT_FLOOR_MSHow many milliseconds sooner the faster Hub must be predicted to answer, as well as meeting the ratio, before it goes ahead. A number of 0 or more; anything else reads as the default.20000
HUB_POOL_FIRST_BYTE_TIMEOUT_MSHow long Hub waits for an engine to begin a streamed answer before it gives up on that engine and tries the next candidate, in milliseconds. A long prompt gets longer: the wait is never less than the prompt’s estimated token count divided by HUB_POOL_MIN_PREFILL_TOKENS_PER_SEC. Values below 15000 are raised to 15000. Hub waits the same way on its own engines.300000 (5 minutes)
HUB_POOL_MIN_PREFILL_TOKENS_PER_SECThe slowest prompt-reading speed, in tokens per second, that Hub budgets a request for. A lower value waits longer for a slow engine before moving the work, and a higher value gives up sooner. A number of 1 or more.50
HUB_POOL_COMPLETION_TIMEOUT_MSHow long Hub waits for a whole answer that is not streamed before it gives up on that engine, in milliseconds. It is never less than the first-byte wait above.300000 (5 minutes)
HUB_POOL_PREFIX_AFFINITY_TTL_MSHow long a conversation stays with the engine that last took it, in milliseconds, so its next request reaches the engine that still has the start of the prompt cached. A number of 1000 or more.600000 (10 minutes)
HUB_POOL_ROUTING_LOG_SIZEHow many routing decisions Hub keeps in memory for Recent routing and cihub pool log. A whole number from 200 to 10000, read when the Hub starts: a smaller number reads as 200 and a larger one as 10000. The log is lost when the Hub restarts.200

Model Context Protocol (MCP)

VariableDescriptionDefault
MCP_ENABLEDSet to false to stop loading the MCP endpoint at /api/mcp. cihub mcp setup and cihub mcp shutdown set it in the env file of the current folder, so they apply to a source checkout.true
MCP_RATE_LIMITRequests each client address may make to the MCP endpoint in one window.300
MCP_RATE_TTL_MSLength of that window, in milliseconds.60000
MCP_DNS_REBINDING_PROTECTIONSet to true to accept MCP requests only for known host names.false
MCP_ALLOWED_HOSTSExtra host names to accept when DNS rebinding protection is on, as a comma-separated list. Hub already accepts localhost, its container names, DOMAIN, and LOCAL_DOMAIN.(none)

MCP_API_KEY is no longer a credential. Hub ignores it and drops it from its resolved environment. Create keys in Settings → Security or with cihub api-key create. See Companion agent.

Feature flags

VariableDescriptionDefault
DEMO_MODEEnables demo mode with a limited, read-only feature set.false
GUEST_DASHBOARDAllows unauthenticated access to a limited dashboard.false
DISABLE_PASSWORD_RESETDisables password reset flows.true
ALLOW_AUTO_THEMESEnables seasonal theme changes based on time of year.true
THEME_BASE, THEME_COLORDefault dashboard theme. The Settings page changes them.gray, blue
PERSIST_TRAEFIK_CONFIGPresent in Settings and the environment, but Hub’s startup code does not read it: Hub rewrites its Traefik files on every start. See Folder structure.false
ADVANCED_SETTINGSShows the Advanced settings section on the Settings tab, which holds fields such as Local domain and Maximum backups to keep. The separate Advanced Mode switch under Experience controls the extra options in install dialogs.false
EXPERIMENTAL_INSECURE_COOKIEUses insecure cookies. Only for HTTP-only dev setups.false
MAX_BACKUPSNumber of backups to keep per app, from 0 to 100. 0 means no limit when you take a backup, but Hub’s weekly cleanup still trims each app to its newest five. An app’s own backup limit overrides it when you take a backup. See Backup and restore.0

Error monitoring (Sentry)

Crash reports go to Sentry only when a reporting address is present. The Hub image and the desktop app ship with one. Three switches turn reporting off even then: CI_LOCAL_ONLY, CI_TELEMETRY, and the Allow anonymous error monitoring switch in the General settings card on the Settings tab. See Crash reporting for what Hub sends.

Settings tab: an Experience card with an Advanced Mode switch that is off, a User settings card with Language set to English and Base theme set to Gray, and a General settings card with Enable guest dashboard and Allow anonymous error monitoring switches, both off, and a Default app CPU limit fieldSettings tab: an Experience card with an Advanced Mode switch that is off, a User settings card with Language set to English and Base theme set to Gray, and a General settings card with Enable guest dashboard and Allow anonymous error monitoring switches, both off, and a Default app CPU limit field
The Settings tab. Advanced Mode is under Experience, and Allow anonymous error monitoring is under General settings. Both switches are off in this capture.
VariableDescriptionDefault
CI_LOCAL_ONLYSet to true for zero outbound reporting. It beats every other switch.(unset)
CI_TELEMETRYSet to off (or false, 0, no, disabled) to stop error reporting, even if the image has a reporting address.(unset)
ALLOW_ERROR_MONITORINGSeeds the consent value for a Hub whose settings.json has no allowErrorMonitoring yet. The Settings switch overrides it in both directions.true
SENTRY_DSNBackend Sentry address. Leave it unset to send nothing from the backend.(built in)
SENTRY_ENVSentry environment tag (for example production).CI_HUB_ENVIRONMENT
SENTRY_RELEASEBackend release identifier.CI_HUB_VERSION
SENTRY_TRACES_SAMPLE_RATEShare of backend transactions to sample, from 0 to 1.0.1
SENTRY_ENABLE_METRICS, SENTRY_ENABLE_LOGSSet to false to stop sending Sentry metrics or logs.true
SENTRY_DESKTOP_DSNDesktop app Sentry address.(built in)

VITE_SENTRY_DSN and VITE_SENTRY_RELEASE set the dashboard’s own Sentry address and release. They are compiled into the dashboard when the Hub image is built, so changing them at runtime has no effect.

Legacy variables

These variables are still accepted for backward compatibility but should not be used in new installations.

VariableReplaced byNotes
DNS_IP(nothing)Still read, with a default of 9.9.9.9, but no part of Hub uses the value.
NGINX_PORTHTTP_PORTHub uses Traefik, not nginx. Variable still read by the config schema.
NGINX_PORT_SSLHTTPS_PORTHub uses Traefik, not nginx. Variable still read by the config schema.
HEADSCALE_PREAUTH_KEYTAILSCALE_AUTHKEYOld VPN naming from an earlier release.
RUNTIPI_*, TIPI_*CI_HUB_*Path and directory variables from the Runtipi-era layout, for example RUNTIPI_APP_DATA_PATH and TIPI_DATA_DIR. Hub maps them to CI_HUB_APP_DATA_PATH, CI_HUB_DATA_DIR, and their siblings.
MCP_ALLOW_DESTRUCTIVEPer-key capabilityHub drops it from its resolved environment. Set a key’s capability instead.

App variables

Hub writes an app.env file for each installed app, and every service in the app reads it. Hub does not copy its own environment into it. An app inherits only an allowlist of Hub values: DOMAIN, LOCAL_DOMAIN, ROOT_FOLDER_HOST, CI_HUB_APP_DATA_PATH, INTERNAL_IP, ARCHITECTURE, TZ, LOG_LEVEL, NODE_ENV, CI_HUB_VERSION, DEMO_MODE, and CI_CLOUD_URL. Hub’s secrets, such as JWT_SECRET and the database and queue passwords, never reach an app.

Hub then sets these app-scoped variables each time it writes the file: on install and whenever the app starts, restarts, updates, or resets.

VariableDescription
APP_URNThe app’s URN, <app-name>:<store>.
APP_ID<app-name>-<store>, the app’s unique identifier.
APP_NAME, APP_STORE_IDThe two halves of the URN.
APP_PORTThe port the app listens on. Set only when the app declares one.
APP_DATA_DIRAbsolute host path where the app’s persistent data lives.
APP_IMAGE_TAGThe app’s version from its manifest.
APP_HOSTNAMEThe address the app binds, such as 0.0.0.0.
APP_INTERNAL_AUTHORITY<host>:<port> for the app’s local address.
APP_EXPOSEDtrue if the app has a public address, otherwise false.
APP_SCHEME, APP_PROTOCOLhttp or https.
APP_PUBLIC_HOSTNAME, APP_PUBLIC_URLThe public host name and URL. Set only when the app is exposed.
APP_DOMAINThe app’s public host name if exposed, otherwise host:port.
APP_EXPOSED_DOMAINThe public host name. Set only when the app is exposed.
APP_LOCAL_DOMAINThe name the app answers to on your local network, <subdomain>.<LOCAL_DOMAIN>. Set when local host-name routing is on.
APP_HOSTThe public host name if exposed, otherwise the local host, without a port.
APP_URLThe full URL a browser uses to open the app.
APP_BASE_URL, APP_BASE_HOST, APP_BASE_WSS_ORIGINThe base URL, its host, and its WebSocket origin, for exposable apps.

An app that declares hub_integration in its manifest also receives the variables described in Companion agent and AI and inference.

Last updated on