agentsclimarketplace

Hermes docker user remap volume

Skill kjuhwa/skills-hub/skills/devops/hermes-docker-user-remap-volume

Docker entrypoint that remaps non-root UID/GID to match host volume ownership and bootstraps config files.From its SKILL.md

Install
npx -y skills add kjuhwa/skills-hub --skill hermes-docker-user-remap-volume

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.8 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

Docker User-Remap Entrypoint with Volume Bootstrap

Context

When a Docker image runs as a fixed UID (e.g. useradd -u 10000) but the user mounts a volume owned by a different UID, the container either can't write (permission denied) or writes files the host user can't later access. The fix is runtime UID remap + chown, with a safe fallback for rootless Podman.

When to use

  • Your image carries a non-root user with a built-in UID.
  • Users mount ~/.myapp or similar — ownership will vary across hosts.
  • You also need to seed default config files (.env, config.yaml, SOUL.md) into the volume if missing.

Procedure

1. Dockerfile installs gosu from an official source

FROM tianon/gosu:1.19-trixie@sha256:... AS gosu_source
...
COPY --chmod=0755 --from=gosu_source /gosu /usr/local/bin/

(Dockerfile:2,21)

Using a pinned digest + multi-stage copy avoids trusting a mutable tag.

2. Bake a default non-root UID

RUN useradd -u 10000 -m -d /opt/data hermes
ENV HERMES_HOME=/opt/data
VOLUME [ "/opt/data" ]
ENTRYPOINT [ "/opt/hermes/docker/entrypoint.sh" ]

3. Entrypoint: remap, chown, drop privs

#!/bin/bash
set -e
HERMES_HOME="${HERMES_HOME:-/opt/data}"
INSTALL_DIR="/opt/hermes"

if [ "$(id -u)" = "0" ]; then
    if [ -n "$HERMES_UID" ] && [ "$HERMES_UID" != "$(id -u hermes)" ]; then
        usermod -u "$HERMES_UID" hermes
    fi
    if [ -n "$HERMES_GID" ] && [ "$HERMES_GID" != "$(id -g hermes)" ]; then
        # -o allows non-unique GID (macOS GID 20 "staff" may exist
        # as "dialout" in the Debian container image)
        groupmod -o -g "$HERMES_GID" hermes 2>/dev/null || true
    fi
    actual_hermes_uid=$(id -u hermes)
    if [ "$(stat -c %u "$HERMES_HOME")" != "$actual_hermes_uid" ]; then
        chown -R hermes:hermes "$HERMES_HOME" 2>/dev/null || \
            echo "Warning: chown failed (rootless container?) — continuing"
    fi
    exec gosu hermes "$0" "$@"
fi

(docker/entrypoint.sh:1-37)

4. Rootless Podman: tolerate chown failure

In rootless Podman, the container's "root" is a host-side unprivileged UID mapped in. chown inside will fail — and that's fine; the volume is already owned by the mapped user on the host. The || echo Warning… makes the entrypoint not abort on this expected failure.

5. -o on groupmod for non-unique GIDs

macOS users have GID 20 (staff). That collides with the Debian dialout group in the container. groupmod -o allows the remap anyway. Without it, the entire bind mount becomes unusable for Mac users.

6. After privilege drop: bootstrap the volume

Run as hermes, activate venv, then seed default files if they don't exist:

source "${INSTALL_DIR}/.venv/bin/activate"

mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home}

[ ! -f "$HERMES_HOME/.env" ]        && cp "$INSTALL_DIR/.env.example"         "$HERMES_HOME/.env"
[ ! -f "$HERMES_HOME/config.yaml" ] && cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml"
[ ! -f "$HERMES_HOME/SOUL.md" ]     && cp "$INSTALL_DIR/docker/SOUL.md"       "$HERMES_HOME/SOUL.md"

# Sync bundled skills into the mounted volume without clobbering user edits
python3 "$INSTALL_DIR/tools/skills_sync.py"

exec hermes "$@"

Note the home/ directory: it exists so subprocesses (git, ssh, gh, npm) have a per-profile $HOME that isn't /root. Without it they'd write into ephemeral, shared container state. See docker/entrypoint.sh:42-49 and referenced issue #4426.

7. Place Playwright / large caches outside the volume

ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright

Volume mounts overlay the image's files at that path, so browsers installed at build time get hidden behind the mount unless you redirect the install path.

Pitfalls

  • Don't chown inside rootless Podman without the fallback. It will hard-fail and exit 1.
  • Don't forget -o on groupmod. It's the single most common "why doesn't the Mac user's volume work" bug.
  • Keep the default UID stable across releases — changing 10000 → 1000 later is a breaking change for existing volumes.
  • Do not pre-create cache/app-specific subdirs in the entrypoint. Let the app populate them on demand so the layout stays in one place (get_hermes_dir() in the app code).

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,790. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.