fix: run container as host PUID/PGID for workspace mounts

node uid 1000 could not read /home/frank (frank=1002); build and compose now use configurable PUID/PGID.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-08-01 11:21:04 +02:00
parent f7d3df128f
commit eb6b0d2fac
6 changed files with 56 additions and 29 deletions
+8 -4
View File
@@ -3,8 +3,12 @@
# OpenRouter (recommended default provider for this stack) # OpenRouter (recommended default provider for this stack)
OPENROUTER_API_KEY=sk-or-v1-replace-me OPENROUTER_API_KEY=sk-or-v1-replace-me
# Match host user that owns the workspace mount (boka: id -u / id -g → 1002)
PUID=1002
PGID=1002
# Host directory mounted at /workspaces inside the containers. # Host directory mounted at /workspaces inside the containers.
# On boka, point at the projects Pi should see, e.g.: # On boka:
# WORKSPACE_HOST_PATH=/home/frank WORKSPACE_HOST_PATH=/home/frank
# For local smoke tests, leave unset to use ./workspaces # For local smoke tests without /home/frank projects:
WORKSPACE_HOST_PATH=./workspaces # WORKSPACE_HOST_PATH=./workspaces
+8 -4
View File
@@ -19,15 +19,18 @@ cd /home/frank/pi.schwenk.online
cp .env.example .env cp .env.example .env
# edit OPENROUTER_API_KEY # edit OPENROUTER_API_KEY
# set WORKSPACE_HOST_PATH to the host dirs Pi should edit, e.g. /home/frank # PUID/PGID = host user (frank on boka: 1002) — must match `id -u` / `id -g`
# WORKSPACE_HOST_PATH=/home/frank
mkdir -p data/pi-web data/pi-agent workspaces mkdir -p data/pi-web data/pi-agent
# Container runs as uid 1000 (node); fix ownership if needed: chown -R "$(id -u):$(id -g)" data
sudo chown -R 1000:1000 data workspaces
docker compose build docker compose build
docker compose up -d docker compose up -d
docker compose logs -f --tail=100 docker compose logs -f --tail=100
# smoke: should list your projects, not Permission denied
docker compose exec web ls /workspaces/eselhoefe.de
``` ```
## Verify ## Verify
@@ -56,3 +59,4 @@ docker compose build --no-cache && docker compose up -d
- No host port publish; only Traefik on network `traefik` reaches `:8504` - No host port publish; only Traefik on network `traefik` reaches `:8504`
- Middleware is referenced only (`authelia@docker`); ForwardAuth is defined on Authelia - Middleware is referenced only (`authelia@docker`); ForwardAuth is defined on Authelia
- `pi-coding-agent` pinned to `0.82.1` for current `pi-web` peer range - `pi-coding-agent` pinned to `0.82.1` for current `pi-web` peer range
- Container runs as `PUID`/`PGID` (default 1002) so bind mounts under `/home/frank` are readable
+15 -10
View File
@@ -1,7 +1,14 @@
# Pi Coding Agent + PI WEB (Plain Docker) # Pi Coding Agent + PI WEB (Plain Docker)
# Peer pin: pi-web requires pi-coding-agent >=0.82.1 <0.83 # Peer pin: pi-web requires pi-coding-agent >=0.82.1 <0.83
# Run as host UID/GID so bind-mounted /home/frank (etc.) is readable.
ARG PUID=1002
ARG PGID=1002
FROM node:24-bookworm-slim FROM node:24-bookworm-slim
ARG PUID=1002
ARG PGID=1002
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
bash \ bash \
@@ -19,28 +26,26 @@ RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.1 \
&& npm install -g @jmfederico/pi-web@1.202607.3 --allow-scripts=node-pty \ && npm install -g @jmfederico/pi-web@1.202607.3 --allow-scripts=node-pty \
&& npm cache clean --force && npm cache clean --force
# Drop build toolchain from runtime image layer weight is optional; keep for native package installs RUN groupadd -g "${PGID}" pi \
# Re-install without g++ would break future pi packages needing compile — leave tools in. && useradd -u "${PUID}" -g "${PGID}" -m -d /home/pi -s /bin/bash pi \
&& mkdir -p /data/pi-web /workspaces /home/pi/.pi/agent /home/pi/.config/pi-web \
&& chown -R pi:pi /data /workspaces /home/pi
RUN mkdir -p /data/pi-web /workspaces /home/node/.pi/agent /home/node/.config/pi-web \ COPY --chown=pi:pi config/pi-web.config.json /etc/pi-web/config.json
&& chown -R node:node /data /workspaces /home/node/.pi /home/node/.config
COPY --chown=node:node config/pi-web.config.json /etc/pi-web/config.json ENV HOME=/home/pi \
ENV HOME=/home/node \
PI_WEB_CONFIG=/etc/pi-web/config.json \ PI_WEB_CONFIG=/etc/pi-web/config.json \
PI_WEB_DATA_DIR=/data/pi-web \ PI_WEB_DATA_DIR=/data/pi-web \
PI_CODING_AGENT_DIR=/home/node/.pi/agent \ PI_CODING_AGENT_DIR=/home/pi/.pi/agent \
PI_WEB_SESSIOND_SOCKET=/data/pi-web/sessiond.sock \ PI_WEB_SESSIOND_SOCKET=/data/pi-web/sessiond.sock \
PI_WEB_HOST=0.0.0.0 \ PI_WEB_HOST=0.0.0.0 \
PI_WEB_PORT=8504 \ PI_WEB_PORT=8504 \
PI_WEB_ALLOWED_HOSTS=pi.schwenk.online \ PI_WEB_ALLOWED_HOSTS=pi.schwenk.online \
PATH="/usr/local/bin:${PATH}" PATH="/usr/local/bin:${PATH}"
USER node USER pi
WORKDIR /workspaces WORKDIR /workspaces
EXPOSE 8504 EXPOSE 8504
# Default overridden per-service in compose.yml
CMD ["pi-web-server"] CMD ["pi-web-server"]
+3 -1
View File
@@ -13,7 +13,9 @@ Implemented Compose stack for PI WEB + Pi behind Traefik/Authelia. See [DEPLOY.m
- `web` HTTP `200` on `:8504`; `pi --version``0.82.1` - `web` HTTP `200` on `:8504`; `pi --version``0.82.1`
- Full Authelia/DNS/TOTP path: only on boka after DNS + `.env` key - Full Authelia/DNS/TOTP path: only on boka after DNS + `.env` key
Stack stopped locally after smoke (`docker compose down`). ## UID fix (2026-08-01)
Container must run as host `frank` (1002:1002), not image `node` (1000). Rebuild with `PUID`/`PGID` after pull.
## Authelia handoff (locked) ## Authelia handoff (locked)
+2 -1
View File
@@ -46,8 +46,9 @@ Fränky — single trusted user; remote control surface for agent work under `/h
- **Compose:** `compose.yml` — services `sessiond` + `web`, image built from `Dockerfile` - **Compose:** `compose.yml` — services `sessiond` + `web`, image built from `Dockerfile`
- **Networks:** external `traefik` - **Networks:** external `traefik`
- **Auth:** Authelia portal `https://auth.schwenk.online`; middleware `authelia@docker`; ACL `two_factor` for this host - **Auth:** Authelia portal `https://auth.schwenk.online`; middleware `authelia@docker`; ACL `two_factor` for this host
- **Data:** `./data/pi-web` (PI WEB state + sessiond socket), `./data/pi-agent``~/.pi/agent` (sessions, packages, auth) - **Data:** `./data/pi-web` (PI WEB state + sessiond socket), `./data/pi-agent``/home/pi/.pi/agent` (sessions, packages, auth)
- **Workspaces:** host path via `WORKSPACE_HOST_PATH` → container `/workspaces` - **Workspaces:** host path via `WORKSPACE_HOST_PATH` → container `/workspaces`
- **UID:** `PUID`/`PGID` in `.env` must match host owner of the mount (boka frank = 1002)
- **Secrets:** `OPENROUTER_API_KEY` in `.env` (see `.env.example`) - **Secrets:** `OPENROUTER_API_KEY` in `.env` (see `.env.example`)
### Authelia handoff (consumed) ### Authelia handoff (consumed)
+20 -9
View File
@@ -1,54 +1,65 @@
# PI WEB + Pi Coding Agent behind Traefik + Authelia (external) # PI WEB + Pi Coding Agent behind Traefik + Authelia (external)
# Deploy: /home/frank/pi.schwenk.online → https://pi.schwenk.online # Deploy: /home/frank/pi.schwenk.online → https://pi.schwenk.online
#
# PUID/PGID must match the host user that owns WORKSPACE_HOST_PATH (boka: frank=1002).
services: services:
sessiond: sessiond:
build: . build:
context: .
args:
PUID: ${PUID:-1002}
PGID: ${PGID:-1002}
image: pidevremote-pi:local image: pidevremote-pi:local
container_name: pi-sessiond container_name: pi-sessiond
restart: unless-stopped restart: unless-stopped
user: "${PUID:-1002}:${PGID:-1002}"
command: ["pi-web-sessiond"] command: ["pi-web-sessiond"]
env_file: env_file:
- .env - .env
environment: environment:
HOME: /home/node HOME: /home/pi
PI_WEB_CONFIG: /etc/pi-web/config.json PI_WEB_CONFIG: /etc/pi-web/config.json
PI_WEB_DATA_DIR: /data/pi-web PI_WEB_DATA_DIR: /data/pi-web
PI_WEB_SESSIOND_SOCKET: /data/pi-web/sessiond.sock PI_WEB_SESSIOND_SOCKET: /data/pi-web/sessiond.sock
PI_CODING_AGENT_DIR: /home/node/.pi/agent PI_CODING_AGENT_DIR: /home/pi/.pi/agent
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-} OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
volumes: volumes:
- ./data/pi-web:/data/pi-web - ./data/pi-web:/data/pi-web
- ./data/pi-agent:/home/node/.pi/agent - ./data/pi-agent:/home/pi/.pi/agent
- ${WORKSPACE_HOST_PATH:-./workspaces}:/workspaces - ${WORKSPACE_HOST_PATH:-./workspaces}:/workspaces
networks: networks:
- internal - internal
web: web:
build: . build:
context: .
args:
PUID: ${PUID:-1002}
PGID: ${PGID:-1002}
image: pidevremote-pi:local image: pidevremote-pi:local
container_name: pi-web container_name: pi-web
restart: unless-stopped restart: unless-stopped
user: "${PUID:-1002}:${PGID:-1002}"
command: ["pi-web-server"] command: ["pi-web-server"]
depends_on: depends_on:
- sessiond - sessiond
env_file: env_file:
- .env - .env
environment: environment:
HOME: /home/node HOME: /home/pi
PI_WEB_CONFIG: /etc/pi-web/config.json PI_WEB_CONFIG: /etc/pi-web/config.json
PI_WEB_DATA_DIR: /data/pi-web PI_WEB_DATA_DIR: /data/pi-web
PI_WEB_SESSIOND_SOCKET: /data/pi-web/sessiond.sock PI_WEB_SESSIOND_SOCKET: /data/pi-web/sessiond.sock
PI_WEB_HOST: "0.0.0.0" PI_WEB_HOST: "0.0.0.0"
PI_WEB_PORT: "8504" PI_WEB_PORT: "8504"
PI_WEB_ALLOWED_HOSTS: pi.schwenk.online PI_WEB_ALLOWED_HOSTS: pi.schwenk.online
PI_CODING_AGENT_DIR: /home/node/.pi/agent PI_CODING_AGENT_DIR: /home/pi/.pi/agent
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-} OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
volumes: volumes:
- ./data/pi-web:/data/pi-web - ./data/pi-web:/data/pi-web
- ./data/pi-agent:/home/node/.pi/agent - ./data/pi-agent:/home/pi/.pi/agent
- ${WORKSPACE_HOST_PATH:-./workspaces}:/workspaces - ${WORKSPACE_HOST_PATH:-./workspaces}:/workspaces
# No host port publish — Traefik reaches the container on the traefik network
expose: expose:
- "8504" - "8504"
networks: networks: