---
title: Configuration
description: Environment variables, volume mounts, and the 1:1 path rule.
---

Sencho's deployment is configured through environment variables and Docker volume mounts set on the Sencho container itself: there is no config file to edit inside the container. This page covers that deployment layer.

Operational settings (host alerts, data retention, image-update automation, mesh networking, registries, notifications, and more) are configured in the app after first boot, in the Settings Hub. See the [Settings Reference](/reference/settings) for that runtime layer.

## Required environment variables

| Variable | Description |
|----------|-------------|
| `COMPOSE_DIR` | Absolute path to the directory that contains your Compose stacks. Every subdirectory inside becomes a stack in Sencho. Falls back to `/app/compose` if unset, but per the [1:1 path rule](#compose-directory-the-11-path-rule) you should always set this explicitly to match the host path you mount. |

<Note>
  **JWT_SECRET is generated automatically.** Sencho creates a secure random signing key during initial setup and stores it in its database. You do not need to provide one.
</Note>

### How Sencho organizes your compose directory

When you point `COMPOSE_DIR` at a directory, Sencho expects each stack to live in its own subdirectory. If you create a stack through the UI, Sencho automatically creates a subfolder and places a blank `compose.yaml` inside it. Sencho does not move or "capture" existing files; it simply treats every subdirectory as a separate stack.

## Optional environment variables

| Variable | Default | Description |
|----------|---------|-------------|
| `DATA_DIR` | `/app/data` | Directory where Sencho stores its SQLite database, node registry, and cached metrics. |
| `FRONTEND_URL` | *(empty)* | Frontend origin for CORS. Only needed if the UI is served from a different domain than the API. Leave empty for same-origin setups. |
| `NODE_ENV` | `production` | Set automatically in the Docker image. Only change this for local development. |
| `SENCHO_USER` | *(unset)* | When set to a username present inside the container (`sencho` is pre-created for this purpose), the entrypoint drops privileges to that user at startup instead of running as `root`. See [Running as a non-root user](#running-as-a-non-root-user) below. |
| `API_RATE_LIMIT` | `200` | Global API requests per minute per user session. Applies in production only; development uses a fixed higher cap. Authenticated requests are keyed by user ID, unauthenticated by IP. Internal node-to-node traffic bypasses this limit. |
| `API_POLLING_RATE_LIMIT` | `300` | Rate limit for dashboard polling endpoints, in requests per minute. Applies in production only; development uses a fixed higher cap. Raise it for environments with many concurrent browser sessions behind shared NAT. |

## Advanced environment variables

These tune optional subsystems. Most deployments never set them; the defaults are sensible.

| Variable | Default | Description |
|----------|---------|-------------|
| `TRIVY_BIN` | *(unset)* | Path to a host-installed [Trivy](/operations/trivy-setup) binary for image vulnerability scanning. Sencho prefers a managed install under `DATA_DIR/bin/trivy`, then this path, then `trivy` on `PATH`. |
| `SENCHO_MESH_SUBNET` | *(auto)* | CIDR for this node's `sencho_mesh` network. When unset, Sencho picks the first free `/24` from its candidate list or adopts an existing mesh subnet. Set one only to avoid an overlap with another network on the host. See [Sencho Mesh](/features/sencho-mesh). |
| `GITSOURCE_MAX_CLONE_BYTES` | `104857600` | Maximum bytes a single [Git Source](/features/git-sources) clone may download before it is aborted (100 MB). A shallow Compose clone is tiny; raise it only if you track Compose files in a legitimately large repository. |
| `SENCHO_PUBLIC_URL` | *(request host)* | Set on the primary instance. Its externally reachable `http(s)://` URL, no trailing slash, baked into pilot enrollment so remote agents dial the public hostname rather than the address the admin used at setup. |
| `SENCHO_COMPOSE_STALL_TIMEOUT_MS` | `600000` | Idle-output backstop for deploy and update Compose steps (pull and recreate). If a step produces no output for this long while still running, Sencho stops it so a hung image pull surfaces a clear failure and the in-app recovery actions instead of spinning. Raise it on slow links or for heavy local image builds. |
| `SENCHO_ZFS_ARCSTATS_PATH` | *(auto)* | Path **inside the container** to the OpenZFS ARC kstat file, for [ZFS ARC-aware host memory](#zfs-arc-aware-host-memory). Sencho checks this path first, then `/host/proc/spl/kstat/zfs/arcstats`, then `/proc/spl/kstat/zfs/arcstats`. Set it only when your ARC stats live at a non-standard path. |

Running a remote host as a pilot agent uses four more variables (`SENCHO_MODE`, `SENCHO_PRIMARY_URL`, `SENCHO_ENROLL_TOKEN`, and `SENCHO_PILOT_CA_FILE`), set only on the remote agent container. Sencho bakes them into the enrollment Compose file it generates, so you rarely write them by hand. See [Pilot Agent](/features/pilot-agent) for the full enrollment walkthrough.

## ZFS ARC-aware host memory

On OpenZFS hosts (TrueNAS SCALE, Proxmox, ZFS on Ubuntu or Debian) the ZFS ARC cache can hold a large share of RAM. ARC is reclaimable on demand, but the Linux kernel reports it as unavailable, so a naive reading counts ARC as used memory and can raise false host-memory alerts.

Sencho reads the ARC kstat when it is available and adds the reclaimable portion back into available memory, so the dashboard memory gauge and host RAM alerts reflect real memory pressure. When no ARC stats are readable the behavior is unchanged.

The ARC kstat is usually visible inside the container at `/proc/spl/kstat/zfs/arcstats` with no extra configuration. If your runtime does not expose it, mount it read-only:

```yaml
volumes:
  - /proc/spl/kstat/zfs/arcstats:/host/proc/spl/kstat/zfs/arcstats:ro
```

Sencho checks `SENCHO_ZFS_ARCSTATS_PATH`, then `/host/proc/spl/kstat/zfs/arcstats`, then `/proc/spl/kstat/zfs/arcstats`. Set `SENCHO_ZFS_ARCSTATS_PATH` only if your ARC stats live somewhere else inside the container.

## Listen port

Sencho always listens on `1852` inside the container. The port is fixed and is not read from an environment variable. To expose Sencho on a different host port, remap with Docker's `-p` flag (or the `ports:` key in your compose file):

```yaml
ports:
  - "8080:1852"   # host 8080 to container 1852
```

Behind a reverse proxy you can keep the standard mapping and let the proxy own the public port.

## Container user

Sencho runs as `root` inside the container by default, matching Portainer, Dockge, Komodo, and Yacht. This is required so Sencho can always write to your compose folders, even when a stack container (commonly anything from `linuxserver/*`) has chowned its own bind mount to another UID. Mounting `/var/run/docker.sock` already grants root-equivalent access to the host, so running the Sencho process itself as root does not change the effective privilege boundary.

**What this means for you:**

- Files Sencho creates inside your compose directory (new stacks, edited `compose.yaml`, generated `.env` files) will be owned by `root` on the host. If you edit those files outside Sencho using your own editor, you will need `sudo` or a one-time `chown`.
- The `/app/data` directory is internal to Sencho; its ownership does not matter for host-side tooling.

### Running as a non-root user

If organisational policy, compliance scanners, or a rootless Docker setup requires the container to drop privileges, set `SENCHO_USER=sencho` in the environment. The entrypoint will chown `/app/data` to the sencho user, match the host's Docker socket group, and exec Node under that user.

```yaml
services:
  sencho:
    image: saelix/sencho:latest
    environment:
      - COMPOSE_DIR=/opt/compose
      - SENCHO_USER=sencho          # opt into non-root mode
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/compose:/opt/compose
      - ./sencho-data:/app/data
```

<Warning>
  Non-root mode can cause filesystem write failures for any stack whose compose folder is owned by a UID other than `sencho`. If you hit "Failed to save stack" or similar errors after opting in, either `chown` the affected stack folder to the sencho user inside the container, or unset `SENCHO_USER` to return to the default (root) mode.
</Warning>

## SSO environment variables

If you use SSO, configure your identity providers via environment variables:

| Variable | Description |
|----------|-------------|
| `SSO_LDAP_ENABLED` | Enable LDAP/AD authentication |
| `SSO_OIDC_GOOGLE_ENABLED` | Enable Google SSO |
| `SSO_OIDC_GITHUB_ENABLED` | Enable GitHub SSO |
| `SSO_OIDC_OKTA_ENABLED` | Enable Okta SSO |
| `SSO_CALLBACK_URL` | External base URL for OAuth callbacks (required behind reverse proxy) |

For end-to-end provider setup walkthroughs, see the [SSO Quickstart →](/getting-started/sso-quickstart). For the full feature reference, see [SSO Authentication →](/features/sso).

## Required volume mounts

### Docker socket

Sencho needs access to the Docker daemon to manage containers:

```yaml
volumes:
  - /var/run/docker.sock:/var/run/docker.sock
```

### Data directory

Sencho's database persists all your settings, nodes, alerts, and metrics history. Mount a named volume or host path so it survives container restarts:

```yaml
volumes:
  - ./sencho-data:/app/data
```

<Warning>
  Without a persistent data mount, Sencho will lose all configuration (including registered nodes, alerts, and settings) every time the container restarts.
</Warning>

### Compose directory - the 1:1 path rule

<Warning>
  This is the most common source of deployment problems. Read carefully.
</Warning>

When Sencho runs `docker compose up`, it does so on your **host machine**. Docker resolves relative volume paths in your Compose files relative to the **host** path of the stack directory, not the path inside the Sencho container.

**The rule:** Mount your Compose directory at the **exact same path** inside the container as it exists on your host.

```yaml
# ✅ Correct - host path matches container path
volumes:
  - /home/user/docker:/home/user/docker
environment:
  - COMPOSE_DIR=/home/user/docker
```

```yaml
# ❌ Wrong - paths differ, relative volumes will break
volumes:
  - /home/user/docker:/app/compose
environment:
  - COMPOSE_DIR=/app/compose
```

If you use a simple path like `/opt/compose` on your host, mount it at `/opt/compose` in the container:

```yaml
volumes:
  - /opt/compose:/opt/compose
environment:
  - COMPOSE_DIR=/opt/compose
```

## Full docker-compose.yml example

```yaml
services:
  sencho:
    image: saelix/sencho:latest
    restart: unless-stopped
    ports:
      - "1852:1852"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./sencho-data:/app/data
      - /opt/compose:/opt/compose          # 1:1 path rule
    environment:
      - COMPOSE_DIR=/opt/compose
      - DATA_DIR=/app/data
```

## Optional: global environment file

If your Compose stacks share common variables (e.g. `PUID`, `PGID`, `TZ`), you can pass an `env_file` to the Sencho container so those variables are available in the host environment when `docker compose` runs:

```yaml
services:
  sencho:
    image: saelix/sencho:latest
    env_file:
      - /opt/compose/globals.env          # shared vars for all stacks
    environment:
      - COMPOSE_DIR=/opt/compose
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/compose:/opt/compose
      - ./sencho-data:/app/data
```

## Reverse proxy setup

Sencho works behind any reverse proxy. The only requirement is that WebSocket connections are forwarded correctly (used for live logs, container terminals, and the host console).

### Nginx

```nginx
server {
    listen 80;
    server_name sencho.yourdomain.com;

    location / {
        proxy_pass http://localhost:1852;
        proxy_http_version 1.1;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 3600s;
    }
}
```

### Nginx with SSL (Let's Encrypt)

```nginx
server {
    listen 80;
    server_name sencho.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name sencho.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/sencho.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/sencho.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:1852;
        proxy_http_version 1.1;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
    }
}
```

Use [Certbot](https://certbot.eff.org/) to obtain and auto-renew certificates: `certbot --nginx -d sencho.yourdomain.com`.

### Traefik (Docker labels)

```yaml
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.sencho.rule=Host(`sencho.yourdomain.com`)"
  - "traefik.http.services.sencho.loadbalancer.server.port=1852"
```

### Traefik with SSL (Let's Encrypt)

```yaml
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.sencho.rule=Host(`sencho.yourdomain.com`)"
  - "traefik.http.routers.sencho.entrypoints=websecure"
  - "traefik.http.routers.sencho.tls.certresolver=letsencrypt"
  - "traefik.http.services.sencho.loadbalancer.server.port=1852"
  # HTTP to HTTPS redirect
  - "traefik.http.routers.sencho-http.rule=Host(`sencho.yourdomain.com`)"
  - "traefik.http.routers.sencho-http.entrypoints=web"
  - "traefik.http.routers.sencho-http.middlewares=redirect-to-https"
  - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
```

<Note>
  Traefik handles WebSocket upgrades automatically for HTTP/1.1 backends. No extra configuration needed.
</Note>

### Caddy

```
sencho.yourdomain.com {
    reverse_proxy localhost:1852
}
```

Caddy automatically obtains and renews SSL certificates via Let's Encrypt. WebSocket connections are forwarded without additional configuration.

## First boot

After starting Sencho, open `http://localhost:1852` in a browser. On a fresh install you land on the **Cold start** card to create the first admin account; every subsequent visit goes to the regular sign-in screen. The [Quickstart](/getting-started/quickstart#first-boot) shows it in detail.

## Where to next

<CardGroup cols={2}>
  <Card title="Stack management" icon="layer-group" href="/features/stack-management">
    Deploy, edit, restart, update, and roll back stacks from the cockpit.
  </Card>
  <Card title="Multi-node" icon="sitemap" href="/features/multi-node">
    Add a remote Sencho instance and manage it from the same console.
  </Card>
  <Card title="Self-hosting" icon="server" href="/operations/self-hosting">
    Resource recommendations, networking, and Docker socket security.
  </Card>
  <Card title="SSO Quickstart" icon="key" href="/getting-started/sso-quickstart">
    End-to-end provider setup for OIDC, LDAP, and Active Directory.
  </Card>
</CardGroup>
