Part of #2
D4 — UID/GID model for bind-mounted repos
Labels: wayfinder:grilling status:closed
Blocks: D6, D8
Resolution
Model: PUID / PGID env with a root-entrypoint script that adjusts on start (LinuxServer.io pattern).
Rationale: chartr's bind-mount contract (D5) accepts arbitrary host repo trees owned by arbitrary UIDs. A fixed nonroot UID would push every non-1000 host operator to --user overrides and still leave root-owned bytes on hosts that forget. PUID/PGID handles it without operator ceremony, at the cost of a shell entrypoint layer.
Spec fragment:
- Image final
USER is root — required so the entrypoint can chown and drop privileges. This is the LSIO norm and is compatible with chartr running non-listening-on-privileged-ports (default 8787).
- Entrypoint script (e.g.
/usr/local/bin/chartr-entrypoint) — POSIX shell, minimal:
- Read
PUID (default 1000) and PGID (default 1000).
- Ensure a
chartr user + group exist at those numeric ids (create if absent; usermod/groupmod if drift).
chown -R chartr:chartr /data /config (idempotent, cheap; the two chartr-owned volumes only — never touches bind-mounted repos).
exec chartr as chartr via su-exec (alpine) or gosu (debian-slim) — no shell between; PID 1 replacement.
- Child processes chartr spawns (agents, PTYs) inherit the su-exec'd uid — no special handling in chartr's Go code. Explicit non-goal so implementers don't add uid-switching logic.
- PID-1 reaper: cross-reference to D6. Since su-exec/gosu replaces PID 1 with chartr, and chartr spawns PTY children, spec
tini (or dumb-init) between the entrypoint and chartr for zombie reaping under docker stop. D6 owns which reaper and its invocation.
Compose surface:
- Demonstrate with
environment: PUID=1000, PGID=1000 in the compose example (D8). Users on non-1000 hosts set them via .env (PUID=${UID}, PGID=${GID}).
- Do NOT include
user: in the compose file — it bypasses the root entrypoint, and the PUID/PGID chown step never runs. Docs must call this out.
- macOS Docker Desktop: uid remapping is transparent; PUID/PGID still work but the chown step is largely a no-op there.
Downstream impact:
- D5 (agent-CLI bind-mount contract) — agent binaries must be executable by uid
PUID (host-set) — a normal file mode concern, not a new decision.
- D6 (Dockerfile) — final
USER root, COPY the entrypoint script, RUN install su-exec (alpine) or gosu (debian), ENTRYPOINT ["/usr/local/bin/chartr-entrypoint"], plus tini/dumb-init selection.
- D8 (compose + docs) — PUID/PGID env line; troubleshooting entry for "root-owned files appeared on my host" →
user: accidentally set, or container not started as root.
Question
Bind-mounted host repos must be readable and writable by the container process without leaving root-owned files on the host. Decide:
- Run as a fixed nonroot UID/GID baked into the image (e.g.
1000:1000) and document --user overrides, or support PUID/PGID env vars with an entrypoint that adjusts on start (LinuxServer.io pattern), or require the user to pass --user "$(id -u):$(id -g)"?
- Which of those plays best with chartr writing into its config/state volume AND into bind-mounted repos owned by arbitrary host UIDs?
- macOS Docker Desktop implications (uid remapping is largely transparent there) vs. rootful Docker on Linux (where it isn't).
- Impact on child processes chartr spawns (agents, PTYs) — they inherit the same uid.
Deliverable: chosen model + a short rationale + the exact docker run / compose snippet an operator would use.
Part of #2
D4 — UID/GID model for bind-mounted repos
Labels:
wayfinder:grillingstatus:closedBlocks: D6, D8
Resolution
Model:
PUID/PGIDenv with a root-entrypoint script that adjusts on start (LinuxServer.io pattern).Rationale: chartr's bind-mount contract (D5) accepts arbitrary host repo trees owned by arbitrary UIDs. A fixed nonroot UID would push every non-
1000host operator to--useroverrides and still leave root-owned bytes on hosts that forget. PUID/PGID handles it without operator ceremony, at the cost of a shell entrypoint layer.Spec fragment:
USERisroot— required so the entrypoint canchownand drop privileges. This is the LSIO norm and is compatible with chartr running non-listening-on-privileged-ports (default8787)./usr/local/bin/chartr-entrypoint) — POSIX shell, minimal:PUID(default1000) andPGID(default1000).chartruser + group exist at those numeric ids (create if absent;usermod/groupmodif drift).chown -R chartr:chartr /data /config(idempotent, cheap; the two chartr-owned volumes only — never touches bind-mounted repos).execchartr aschartrviasu-exec(alpine) orgosu(debian-slim) — no shell between; PID 1 replacement.tini(ordumb-init) between the entrypoint and chartr for zombie reaping underdocker stop. D6 owns which reaper and its invocation.Compose surface:
environment: PUID=1000, PGID=1000in the compose example (D8). Users on non-1000hosts set them via.env(PUID=${UID},PGID=${GID}).user:in the compose file — it bypasses the root entrypoint, and the PUID/PGID chown step never runs. Docs must call this out.Downstream impact:
PUID(host-set) — a normal file mode concern, not a new decision.USER root,COPYthe entrypoint script,RUNinstallsu-exec(alpine) orgosu(debian),ENTRYPOINT ["/usr/local/bin/chartr-entrypoint"], plus tini/dumb-init selection.user:accidentally set, or container not started as root.Question
Bind-mounted host repos must be readable and writable by the container process without leaving root-owned files on the host. Decide:
1000:1000) and document--useroverrides, or supportPUID/PGIDenv vars with an entrypoint that adjusts on start (LinuxServer.io pattern), or require the user to pass--user "$(id -u):$(id -g)"?Deliverable: chosen model + a short rationale + the exact
docker run/ compose snippet an operator would use.