Run containers as the host user instead of root

Everything the containers wrote into the bind mounts (surreal_data, notebook_data)
was owned by root, so the host user could not delete or back up his own podcast
data — prune_podcast_data.py and update_stack.sh had to detour through
`docker compose exec` for every rm and tar.

That was not a requirement, just the default: the open_notebook image declares no
USER, and the compose file even overrode surrealdb's own non-root user (65532)
with `user: root`, under the comment "Required for bind mounts on Linux" — which
is not true.

Both services now run as user: "1000:1000". Two things this needs:

- HOME=/tmp for open_notebook. Without it HOME resolves to "/" for a non-root uid,
  uv cannot create /.cache/uv, and api + worker exit 2 at startup. Verified by
  running the image as uid 1000 both ways.
- The data directories must be owned by that uid. Existing data was adopted with a
  throwaway root container (chown -R), no sudo needed.

Both scripts drop the container detour and operate on the host directly, which is
simpler and now honest. smoke_test.sh gained two checks so a silent regression to
root cannot go unnoticed: the container's uid must match the host user, and no
foreign-owned files may exist under the data directories.

Verified: containers run as uid 1000, new files land as dschlueter and are
deletable without sudo, SurrealDB writes as 1000, a source can be created,
embedded and deleted through the API, and the full smoke test is green.

Note this deviates from what the image expects (it assumes root), so it is exactly
the kind of assumption an update can break — hence the smoke-test checks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-07-11 15:14:47 +02:00
commit 9e6d790830
6 changed files with 96 additions and 40 deletions

View file

@ -41,6 +41,26 @@ else
fi
rm -f /tmp/.st_prov
# Laufen die Container als Host-User? Sonst gehoeren alle neu geschriebenen Daten
# root, und Aufraeumen/Sichern auf dem Host scheitert an den Rechten (prune_podcast_data.py,
# update_stack.sh verlassen sich darauf).
uid="$(id -u)"
if [ "$(inapp id -u | tr -d '\r')" = "$uid" ]; then
ok "open_notebook laeuft als Host-User (uid $uid), nicht als root"
else
fail "open_notebook laeuft NICHT als uid $uid — neue Dateien gehoerten root.
user: \"$uid:$(id -g)\" in docker-compose.yml pruefen."
fi
foreign="$(find surreal_data notebook_data ! -user "$USER" 2>/dev/null | wc -l)"
if [ "$foreign" -eq 0 ]; then
ok "keine fremd-eigenen Dateien in surreal_data/ und notebook_data/"
else
fail "$foreign Datei(en) in surreal_data/notebook_data gehoeren nicht $USER
uebernehmen mit: docker run --rm -u 0 -v \"\$PWD:/work\" -w /work \\
--entrypoint chown \$IMAGE -R $uid:$(id -g) surreal_data notebook_data"
fi
sec "Lokale Sprachdienste (vom Container aus)"
if inapp "$PY" -c "