Skip to content

D4: UID/GID model for bind-mounted repos #9

Description

@Teagan42

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:
    1. Read PUID (default 1000) and PGID (default 1000).
    2. Ensure a chartr user + group exist at those numeric ids (create if absent; usermod/groupmod if drift).
    3. chown -R chartr:chartr /data /config (idempotent, cheap; the two chartr-owned volumes only — never touches bind-mounted repos).
    4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions