Skip to Content
DocumentationReferenceFolder structure

Folder structure

Companion Hub keeps its runtime state in one folder on the host, called the data root. Hub’s configuration names it ROOT_FOLDER_HOST. Hub creates the folders below the first time it starts.

Where the data root sits depends on your operating system:

PlatformData root
macOS~/Library/Application Support/companion-hub
Linux$XDG_DATA_HOME/companion-hub, which is ~/.local/share/companion-hub unless you set XDG_DATA_HOME
Windows%APPDATA%\companion-hub

The cihub CLI uses the same location. Set CI_HUB_DATA_DIR to point it somewhere else; the desktop app doesn’t read that variable. On Linux, both ignore an XDG_DATA_HOME that another snap app set, such as the one in a terminal inside a Snap Store editor, and use ~/.local/share/companion-hub instead.

      • settings.json
      • hub-compose.yml
    • docker-compose.prod.yml
    • .env.dev
    • .env

The folder named ci-marketplace is the app store’s identifier. Apps you create yourself sit under a store folder named _user.

Two things live outside the data root:

  • PostgreSQL data is in the Docker named volume ci_hub_pgdata, not in a folder. Stopping Hub does not remove it. Back it up with Docker’s volume tooling if you need Hub’s own database, which holds your users, installed-app records, and registration.
  • The Cloudflare tunnel token is in a tunnel folder next to the data root, not inside it. On Linux that is ~/.local/share/tunnel. Hub reads an older tunnel folder inside the data root only if it finds one there.

Who writes what

Hub and the desktop app generate most of this tree. Your own changes belong in user-config, in the settings.json file that the Settings page writes, and in your app’s own data.

PathWritten byEdit by hand?
app-data/Installed apps, at runtime
apps/Hub, from the app store at install and updateNo — updates overwrite it
backups/Hub, when you back up an app
cache/Hub
logs/Hub and the desktop app
media/Hub creates the folders; you and every app that mounts it fill themYes — visible to every app that mounts it
repos/Hub, for git-based app stores
state/Hubsettings.json only, or use the Settings page
state/traefik/Hub, rewritten each time Hub startsOnly your own files in dynamic/ — see Traefik below
user-config/YouYes — this is the override layer
docker-compose.prod.ymlThe desktop app, copied from its bundle on each launchNo — the desktop app overwrites it
.env.dev and .envThe desktop app, rewritten on each launchNo — the app rewrites both
What writes each path under the data root, and where your own changes belong.

App data

The app-data folder holds each app’s persistent data, one folder per app inside a folder for its store. Each installed app has an app.env file here, which Hub generates, and most catalog apps keep their files in a data subfolder.

You can move this folder. In Settings → General settings, set App data path (appDataPath), or set CI_HUB_APP_DATA_PATH. Hub then puts app-data inside that path instead of inside the data root. Backups stay under the data root.

Apps

The apps folder holds each installed app’s definition: the manifest (config.json), the compose file the store ships (docker-compose.json), the compose file Hub renders from it (docker-compose.yml), and the store listing’s metadata folder. The layout matches app-data: one folder per store, then one per app.

        • config.json
        • docker-compose.json
        • docker-compose.yml

Do not edit files under apps directly, because updates overwrite that tree. Put overrides in user-config instead.

Backups

The backups folder stores app backups as .tar.gz files, grouped by store and app.

        • <app-urn>-<timestamp>.tar.gz

Each archive is named <app-urn>-<timestamp>.tar.gz, where the app URN is <app-name>:<store> and the timestamp is in milliseconds since 1970. It contains three things: the app’s data, its installed definition, and any overrides you keep in user-config. See Backup and restore for how retention works and Back up and prove the restore to verify one.

Logs

The logs folder contains Hub’s log files:

    • error.log
    • app.log
    • desktop.log

error.log records errors, app.log records general Hub output, and desktop.log records what the desktop app did to start the Hub stack. The Settings → Logs tab reads the first two.

Media

Hub creates a shared media tree the first time it starts. Unlike most app data, media is not scoped to a single app, so several apps can mount it. For example, the *arr stack uses the download folders. The data folder is for finished libraries: books, comics, images, movies, music, podcasts, ROMs, and TV. The downloads, torrents, and usenet folders each have watch, complete, and incomplete subfolders.

Repos

The repos folder holds cloned git repositories for git-based app stores. A default Hub gets its catalog from CI Cloud and does not use it. See Custom app stores.

State

The state folder holds instance state that Hub needs at runtime:

  • settings.json holds the settings you change on the Settings page, your Portal credentials, and your AI preferences. It is the one file here you can edit yourself — see Custom settings. Stop Hub before you edit it.
  • seed is the random value Hub derives its secrets and each app’s generated passwords from. Keep it with state/ when you back up or move a Hub.
  • .env.resolved is a snapshot of the environment Hub resolved at startup. Hub rewrites it each time it starts.
  • traefik/ holds Traefik’s configuration and certificates.
  • hardware/ holds probe results about your CPU, memory, and GPU, which Hub reads when it recommends models.
  • docker-engine.json records the Docker engine Hub chose.
  • update-listener.token is the key the desktop app uses so the Hub can hand it an update. The app writes a new one each time it starts. Don’t copy or share it.
  • inference-pinned-models.json records the models you pinned, so they load again after a restart. inference-footprint-sightings.json records how much memory loaded models used, which Hub uses to decide what fits.
  • tailscale-serve-ownership.json records the Tailscale Serve entries Hub published, so it removes only those.
  • CI_HUB_STATUS.md is a status report that Hub refreshes every 15 minutes.

Hub keeps settings.json, seed, and .env.resolved readable only by the account that runs Hub (mode 600), because they hold device keys and the secret Hub derives its passwords from. Hub tightens an older, more open file the next time it starts and never opens a file to other accounts. If cihub runs as a different account than the one that runs Hub, it can’t read the device key in settings.json, and commands such as cihub claim say No device key on this machine.

The data root also holds a .docker/config.json: a copy of your Docker client configuration (registry logins and proxies only, mode 600) that Hub’s own container uses. The desktop app rewrites it at each launch.

Traefik

The state/traefik folder holds Traefik’s static configuration (config/traefik.yml), the dynamic routes Hub writes for the Hub and each app (dynamic/), the Let’s Encrypt storage (acme_storage.json), and your own certificates (tls/).

The desktop app, cihub, and Hub itself copy the bundled traefik.yml and dynamic.yml over the files in this folder when they start, so hand edits to those two files are lost. Hub also writes hub.yml, apps.yml, and port-expose.yml in dynamic/, so don’t use those names for your own files. The Persist Traefik config switch (persistTraefikConfig in settings.json, or PERSIST_TRAEFIK_CONFIG) still exists, but Hub’s startup code does not read it, so it does not stop the rewrite. Hub rewrites only those two files. To add your own routes, save a separate file in state/traefik/dynamic/, which Traefik watches and Hub leaves alone. See Customize Companion Hub compose and Traefik config.

User config

The user-config folder holds your overrides. Each app can have a custom docker-compose.yml and app.env. See Customize app config.

A hub-compose.yml at the root of user-config (or the older name cihub-compose.yml, when hub-compose.yml is absent) does not change the running Hub stack. Hub’s backend reads it only when it builds the compose commands for its log stream and log download. The desktop app and cihub up do not read it, so it can’t change the Hub’s containers or ports. See Customize Companion Hub compose and Traefik config.

        • docker-compose.yml
        • app.env
    • hub-compose.yml

Docker Compose

docker-compose.prod.yml is the compose file for the Hub stack: the Hub, its database and queue, Traefik, and the optional tunnel and VPN containers. The desktop app copies it from its bundle each time it launches, so do not edit it. See Architecture for what it starts.

Environment

The desktop app writes the Hub stack’s environment to .env.dev on macOS and Linux, and to .env on Windows, and keeps an identical copy under the other name. It holds values such as ROOT_FOLDER_HOST, the generated database, queue, and signing secrets, the pinned Hub image, and the Portal address. The app rewrites both files each time it launches from the values it manages, so a line you add by hand is not kept. Hub itself reads the file as /data/.env inside its container.

See Environment variables for every variable Hub reads and for where each kind of change belongs.

The CLI

The cihub command-line tool is not stored in the data root. The desktop app and the Linux packages install it on your PATH. See the CLI reference.

Companion Hub does not write a VERSION file. Settings → System shows the Hub stack version under Hub stack. To compare the CLI and the stack from a terminal, run cihub doctor.

Last updated on