mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-17 14:08:19 +00:00
fix: run as root by default to eliminate stack-folder permission failures (#501)
Every filesystem operation against user compose folders (save, create, deploy, update, rollback, template install, fleet snapshot restore) previously failed with EACCES whenever a stack container had chowned its own bind mount to another UID, which is extremely common with linuxserver/* images and anything that runs as root by default. Running Sencho as root eliminates the entire class of permission bugs at the source and matches the default posture of Portainer, Dockge, Komodo, and Yacht. Mounting /var/run/docker.sock is already equivalent to root-on-host, so the previous non-root hardening provided essentially no additional isolation while breaking real features. Changes: - docker-entrypoint.sh: default path stays root, no GID dance, no privilege drop. Opt-out via SENCHO_USER=sencho restores the legacy behavior bit-for-bit (chown data dir, match Docker socket GID, su-exec to the user). Fails fast if SENCHO_USER names a nonexistent account. Kubernetes / OpenShift forced-non-root compat preserved via the existing id -u = 0 guard. - FileSystemService: delete forceDeleteViaDocker (the ~40-line helper that shelled out to an alpine container to work around EACCES during deleteStack) and simplify deleteStack to a single fsPromises.rm call. Tests updated accordingly. - Dockerfile: keep the sencho user+group pre-created so the opt-out path works out of the box; comments updated to document the new default. - Docs: new "Container user" section in configuration.mdx documenting the root default and the SENCHO_USER opt-out; troubleshooting and self-hosting updated to match.
This commit is contained in:
@@ -27,6 +27,37 @@ When you point `COMPOSE_DIR` at a directory, Sencho expects each stack to live i
|
||||
| `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)* | Optional. 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. |
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -43,19 +43,16 @@ Sencho requires three volume mounts to function correctly:
|
||||
|
||||
## Docker socket security
|
||||
|
||||
Mounting the Docker socket (`/var/run/docker.sock`) grants the container the ability to manage all containers, images, volumes, and networks on the host. This is equivalent to root access on the host machine.
|
||||
Mounting the Docker socket (`/var/run/docker.sock`) grants the container the ability to manage all containers, images, volumes, and networks on the host. This is equivalent to root access on the host machine, regardless of which user the Sencho process itself runs as.
|
||||
|
||||
Sencho mitigates this with privilege dropping:
|
||||
Because the Docker socket is already the effective privilege boundary, Sencho runs as `root` inside the container by default. This matches Portainer, Dockge, Komodo, and Yacht, and ensures Sencho can always write to your compose folders, even when a stack container (for example anything from `linuxserver/*`) has chowned its own bind mount. See [Configuration: Container user](/getting-started/configuration#container-user) for details on the default and the `SENCHO_USER` opt-out.
|
||||
|
||||
1. The container starts as root to fix volume ownership and resolve Docker socket group permissions
|
||||
2. The entrypoint script then drops to a non-root `sencho` user
|
||||
3. All application code runs as the `sencho` user
|
||||
If your environment requires stricter isolation, the Docker socket itself is the right place to focus:
|
||||
|
||||
If your environment requires stricter isolation, consider:
|
||||
|
||||
- Running Sencho on a dedicated Docker host
|
||||
- Using Docker's `--userns-remap` for user namespace isolation
|
||||
- Placing Sencho behind a reverse proxy with authentication (see [Configuration](/getting-started/configuration#reverse-proxy-setup))
|
||||
- Running Sencho on a dedicated Docker host so a compromise cannot reach unrelated workloads
|
||||
- Using Docker's `--userns-remap` for user namespace isolation at the daemon level
|
||||
- Running Sencho behind a reverse proxy with authentication (see [Configuration](/getting-started/configuration#reverse-proxy-setup)) so the UI is not exposed to untrusted networks
|
||||
- Setting `SENCHO_USER=sencho` to drop privileges inside the container as a defense-in-depth measure, accepting that filesystem writes to permission-restricted stack folders will fail in that mode
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -54,35 +54,37 @@ volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
On Linux, the Docker socket is owned by the `docker` group. The Sencho entrypoint detects the socket's GID automatically and adds the internal `sencho` user to the matching group. If you see permission errors despite a correct mount, check that the socket file is readable:
|
||||
If you see permission errors despite a correct mount, check that the socket file exists and is a socket:
|
||||
|
||||
```bash
|
||||
ls -la /var/run/docker.sock
|
||||
# Expected: srw-rw---- 1 root docker ...
|
||||
```
|
||||
|
||||
If the group is not `docker`, the auto-detection still works. Sencho reads the GID from the socket file at startup.
|
||||
Because Sencho runs as `root` inside the container by default, it can read the socket regardless of which group owns it on the host. Permission errors here usually mean the socket is not mounted at all, or it is mounted read-only.
|
||||
|
||||
<Note>
|
||||
If you have opted into non-root mode via `SENCHO_USER=sencho`, the entrypoint will detect the socket's GID and add the sencho user to the matching group automatically. Socket errors in that mode most often mean the `SENCHO_USER` was set to an account that does not exist inside the container.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## "Permission denied" when deleting a stack
|
||||
## "Permission denied" when editing or deleting a stack
|
||||
|
||||
**Symptom:** Clicking **Delete** on a stack fails with a permission error.
|
||||
**Symptom:** Saving `compose.yaml`, creating a new stack, or deleting a stack fails with a permission error.
|
||||
|
||||
**Cause:** Stack directories often contain files owned by root, for example when Docker Compose was run with `sudo`, or when containers write config/data files into the stack directory. Since Sencho runs as a non-root user, the OS denies the removal.
|
||||
Sencho runs as `root` inside the container by default specifically to avoid this class of failure. If you are seeing permission errors, one of the following is true:
|
||||
|
||||
This is especially common when installing Sencho on a server where stacks were originally created outside of Sencho (e.g. via `sudo docker compose up`). Those directories and their contents are root-owned, but Sencho can still delete them.
|
||||
- **You are running in non-root mode** (`SENCHO_USER` is set). In this mode, filesystem writes to stack folders owned by a different UID will fail. Either `chown` the affected stack folder to the opted-in user inside the container, or unset `SENCHO_USER` to return to the default root mode.
|
||||
- **The compose directory mount is read-only.** Check your Sencho container's volume mounts for a trailing `:ro` on the compose dir bind.
|
||||
- **The underlying filesystem is read-only** (e.g. a full disk, a failed disk, or a remote filesystem that lost connectivity).
|
||||
|
||||
**How Sencho handles it:** Sencho automatically detects permission errors during deletion and falls back to a Docker-based cleanup. It spawns a short-lived container that bind-mounts the stack directory and removes the root-owned files. This happens transparently, with no manual intervention needed in the standard Docker setup.
|
||||
|
||||
**If automatic cleanup fails:** The error message will include the directory path. Remove it manually:
|
||||
If you need to clean up a leftover stack directory manually:
|
||||
|
||||
```bash
|
||||
sudo rm -rf /path/to/your/compose/dir/stack-name
|
||||
```
|
||||
|
||||
**Prerequisites:** The Docker socket must be mounted (standard setup). If Sencho cannot access Docker, the fallback will not work. See ["Permission denied" on the Docker socket](#permission-denied-on-the-docker-socket).
|
||||
|
||||
---
|
||||
|
||||
## Login page shows "Something went wrong"
|
||||
|
||||
Reference in New Issue
Block a user