Skip to content

Enhancement: Support Docker rootless mode — fix data directory ownership for non-root container users #1702

Description

@IMNotMax

Description

Summary

When ODS is installed on a host running Docker rootless (via rootlesskit), containers that run as a non-root user (n8n, hermes, whisper, tts, token-spy, privacy-shield, ape, langfuse) fail to start with EACCES: permission denied errors. The data directories created by the installer are owned by the host user (e.g. UID 1000), but in rootless mode, container UIDs are remapped to a different range (starting at 100000), so non-root container users cannot write to them.

Environment

  • OS: Ubuntu (Linux)
  • Docker: rootless mode (rootlesskit + dockerd-rootless.sh)
  • subuid/subgid: maxs:100000:65536
  • ODS version: ods-cli v2.5.3
  • Install path: ~/ods (owned by maxs, UID 1000)

Use Case

Problem

In Docker rootless mode, UID mapping works as follows:

Container UID Host UID
0 (root) 1000 (host user)
1000 (node, ubuntu, appuser, odser) 100000
10000 (hermes) 109000

The installer creates data directories under ~/ods/data/ owned by the host user (UID 1000). Containers running as root (UID 0) work fine because root in the container maps to the host user. However, containers running as a non-root user fail because their container UID maps to a host UID (100000+) that has no write permission on the directory.

Proposed Solution

Symptoms

n8n:

No encryption key found - Auto-generating and saving to: /home/node/.n8n/config
Error: EACCES: permission denied, open '/home/node/.n8n/config'
    at writeFileSync (node:fs:2437:20)
    at InstanceSettings.save (...)

Hermes:

mkdir: cannot create directory '/opt/data': Permission denied
Fixing ownership of /opt/data to hermes (10000)
Warning: chown failed (rootless container?) — continuing anyway
Dropping root privileges
mkdir: cannot create directory '/opt/data': Permission denied

Affected containers

Service Container user Container UID
n8n node 1000
hermes hermes 10000
whisper ubuntu 1000
tts appuser 1000
token-spy odser 1000
privacy-shield 1000
ape ape 100
langfuse-web nextjs 1001

Containers running as root (open-webui, litellm, qdrant, embeddings, perplexica, searxng, llama-server, dashboard, dashboard-api) are not affected.

Reproduction

  1. Install Docker rootless on a Linux host (e.g. dockerd-rootless.sh + rootlesskit).
  2. Ensure subuid/subgid is configured: username:100000:65536.
  3. Run the ODS installer as a non-root user.
  4. Start ODS: ods start
  5. Observe that n8n and hermes fail to start with EACCES / Permission denied errors.

Alternatives Considered

Workaround

Manually fix ownership of each affected data directory using a container with root privileges (which maps to the host user in rootless mode):

# n8n (container UID 1000)
docker run --rm --user 0:0 -v ~/ods/data/n8n:/data alpine sh -c "chown -R 1000:1000 /data"

# hermes (container UID 10000)
docker run --rm --user 0:0 -v ~/ods/data/hermes:/data alpine sh -c "chown -R 10000:10000 /data"

# whisper, tts, token-spy, privacy-shield (container UID 1000)
for svc in whisper tts token-spy privacy-shield; do
  docker run --rm --user 0:0 -v ~/ods/data/$svc:/data alpine sh -c "chown -R 1000:1000 /data"
done

# ape (container UID 100)
docker run --rm --user 0:0 -v ~/ods/data/ape:/data alpine sh -c "chown -R 100:100 /data"

# langfuse (container UID 1001)
docker run --rm --user 0:0 -v ~/ods/data/langfuse:/data alpine sh -c "chown -R 1001:1001 /data"

Then restart: ods restart

Suggested fix

The installer (or ods start / ods restart) should detect rootless mode and fix data directory ownership automatically. Possible approaches:

Option A — Detect rootless and chown after directory creation

After creating data directories in 06-directories.sh, check if Docker is running in rootless mode and, for each service that uses a non-root container user, chown the corresponding data directory to the container's UID:

# Detect rootless mode
_is_rootless_docker() {
    docker info --format '{{.SecurityOptions}}' 2>/dev/null | grep -q rootless
}

if _is_rootless_docker; then
    # Fix ownership for non-root containers
    _chown_data_dir "n8n" 1000
    _chown_data_dir "hermes" 10000
    _chown_data_dir "whisper" 1000
    _chown_data_dir "tts" 1000
    _chown_data_dir "token-spy" 1000
    _chown_data_dir "privacy-shield" 1000
    _chown_data_dir "ape" 100
    _chown_data_dir "langfuse" 1001
fi

Where _chown_data_dir uses a helper container (since the host user may not have sudo):

_chown_data_dir() {
    local svc="$1" uid="$2"
    local dir="$INSTALL_DIR/data/$svc"
    [[ -d "$dir" ]] || return 0
    docker run --rm --user 0:0 -v "$dir:/data" alpine \
        sh -c "chown -R ${uid}:${uid} /data" 2>/dev/null || true
}

Option B — Use docker run --user 0:0 for the chown in the entrypoint

Services like hermes already attempt a chown in their entrypoint (Fixing ownership of /opt/data to hermes (10000)), but it fails because the container has already dropped root privileges or because rootless remapping prevents it. Running the chown before dropping privileges, or using user: "0:0" in the compose file for the initial setup step, would fix this.

Option C — Document the rootless requirement

At minimum, detect rootless mode during ods doctor / ods preflight and warn the user that non-root containers need manual permission fixes, with the workaround commands printed.

Additional context

  • Docker rootless is increasingly common for security best practices (running the Docker daemon as a non-root user).
  • The hermes entrypoint already tries to chown /opt/data but fails with Warning: chown failed (rootless container?) — the detection is there but the fix is incomplete.
  • This affects both fresh installs and reinstalls.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions