Configuration

August 22, 2026 ยท View on GitHub

Tuor can be configured by placing an appropriate config.json either in ~/.config/tuor or in a .tuor directory in the current working directory or any of its parents.

Config files are read as JSONC (= regular JSON + // line and /* block */ comments + trailing commas), using the same parser (by Microsoft) that VSCode uses, too. An informal specification (not by Microsoft) can be found at https://jsonc.org/.

Config inheritance

Configs in child directories inherit from configs in parent directories (and so on), which in turn inherit from the global ~/.config/tuor/config.json. In general, child settings override parent settings, except in the following cases:

  • Env vars, mounts, volumes get merged (shallow merge).
  • Boot commands are concatenated (parent commands run first).
  • Network: Child network mode overrides parent mode; allowed hosts are merged.

Config options

A detailed documentation of all config options is still work in progress. In the meantime, please refer to /src/config/schema.ts.

Variables

Any string value in the config (but not keys) may reference host environment variables, resolved on the host right after the config is loaded (and before it is validated):

{
  "mounts": [
    // $PWD lets you mount wherever you launched Tuor from:
    { "hostPath": "$PWD", "guestPath": "/workspace", "mode": "readwrite" }
  ],
  "resources": { "rootfsSize": "${ROOTFS_SIZE}" },
  // Use $$ for a literal dollar sign:
  "env": { "PROMPT": "$$ " }
}

Both $VAR and ${VAR} are supported (use the braced form when the variable is followed by other word characters, e.g. ${VAR}_suffix). Referencing a variable that is not set on the host is an error.

Example config.json

{
  "network": {
    // "open" for unrestricted access, "restricted" for allowlist
    "mode": "restricted",
    // Allow HTTPS traffic to these hosts
    "allowedHosts": ["*.github.com", "api.anthropic.com"],
    // Like allowedHosts but for hosts pointing at private IPs (which are
    // otherwise blocked to prevent DNS rebinding attacks)
    "allowedInternalHosts": ["local-llm.my.corp"]
  },
  "env": {
    "SOME_VAR": "fixed_value",  // Literal value
    "MY_VAR": "${MY_VARIABLE}_and_a_suffix",  // ${MY_VARIABLE} is interpolated from the host env
    "EDITOR": {},  // Read host var named like the key (i.e. $EDITOR)
    "AUTH_TOKEN": {
      // Injected as a secret: the guest sees a placeholder; the real value
      // (host's $AUTH_TOKEN here, since `value` field is omitted) is substituted only
      // in HTTPS requests to these hosts.
      "secret": true,
      "injectForHosts": ["my-api.hostname.com"]
    },
    "GH_TOKEN": {
      // A secret whose value comes from a differently-named host var:
      "secret": true,
      "value": "$GITHUB_TOKEN",
      "injectForHosts": ["*.github.com"]
    }
  },
  // Shell commands run once, as root, right after boot and before the shell /
  // user command, in the configured workdir. Run in order; a non-zero exit
  // aborts boot (fail fast). Handy for provisioning the guest.
  "bootCommands": [
    "apk add --no-cache ripgrep",
    "mkdir -p /workspace/.cache"
  ],
  "mounts": [
    {
      // Absolute or relative to config.json
      "hostPath": "/path/on/the/host",
      // Can be omitted, in which case guestPath will be set to the resolved
      // (absolute) hostPath.
      "guestPath": "/path/on/the/guest",
      // Will do copy-on-write and persist changes to .tuor/.state/overlays/
      "mode": "overlay",
      // Optional: Explicit paths to hide from the guest
      "ignore": [".env", "secret.key", ".tuor"],
      // Files to read list of ignored files from (think .gitignore). Paths are
      // either host paths or mount-relative paths.
      "ignoreFileRefs": ["host:./tuorignore", "mount:.tuorignore"],
      // Optional: uid/gid presented to the guest for this mount's entries
      // (defaults to guestUser). Display-only: does not change host-side
      // ownership. Either field may be omitted to inherit from guestUser.
      "owner": { "uid": 0, "gid": 0 }
    }
  ],
  // VM resource sizing. Any field left unset falls back to Gondolin's default
  // (1G memory, 2 cpus). Note that `cpus` (the vCPU count) is distinct from
  // `qemu.cpu` (the emulated CPU model).
  "resources": {
    "cpus": 4,          // vCPU count (positive integer)
    "memory": "2G",     // RAM, QEMU syntax (e.g. "512M", "2G")
    // Minimum virtual disk size (COW overlay, so actual host usage stays
    // sparse). Note that the virtual disk will be discarded on VM shutdown,
    // so it is not meant for persisting data across VM boots. (Use mounts &
    // volumes, instead!)
    "rootfsSize": "2G"
  },
  // Guest user (numeric uid/gid) the shell runs under and that mounted
  // directories are presented as owned by. `homedir` (optional, default /root)
  // is the guest home directory used for `~` expansion in guest paths.
  // Constraint: uid/gid must currently be root ({ uid: 0, gid: 0 }).
  "guestUser": { "uid": 0, "gid": 0, "homedir": "/root" },
  // Persistent guest directories without a host backing directory (
  // similar to Docker volumes). Like mounts, they accept an optional `owner`, 
  // defaults to guestUser).
  "volumes": [
    { "guestPath": "~/.claude" }  // Persist Claude Code state
  ],
  // Instead of a string (guest path) you can also provide a mount config here
  // for convenience, e.g.
  // { hostPath: "..", guestPath: "/workspace", mode: "readwrite" }
  "workdir": "/workspace"
}