diff --git a/Dockerfile b/Dockerfile index edaf90b..4554cac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,27 @@ -FROM python:3.12-slim +# Base pinned by digest (the multi-arch index digest of python:3.12-slim, so amd64 and arm64 both +# resolve); the tag is kept for readability only. Refresh both together: +# docker buildx imagetools inspect python:3.12-slim → the top-level "Digest:" line +FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 WORKDIR /app COPY pyproject.toml README.md LICENSE ./ COPY passiveworkers/ passiveworkers/ # [extract] = trafilatura, for real main-content/date extraction on page evidence. The bare # install degrades silently to a regex strip — this image is the README-recommended "just # works" path (research-desk AND coordinator both build from it), so it ships the better -# extractor by default. -RUN pip install --no-cache-dir -e '.[extract]' +# extractor by default. A regular (non-editable) install: the package lands in site-packages, +# owned by root and read-only to the runtime user below. (DL3013 wants a pinned version, but this +# installs the local project, not a registry package.) +# hadolint ignore=DL3013 +RUN pip install --no-cache-dir '.[extract]' +# Run as the unprivileged user `pw`, by numeric ID so `runAsNonRoot`-style checks can verify it. +# UID/GID 1000 matches docker-compose.yml's PW_UID/PW_GID default, so bind-mounted +# ~/.passiveworkers files stay owned by the host user on Linux. /app/data is created here, owned by +# that user, so a fresh named volume mounted there (the coordinator's DB) inherits a writable owner +# instead of root. +RUN groupadd --gid 1000 pw \ + && useradd --uid 1000 --gid pw --create-home --shell /usr/sbin/nologin pw \ + && install -d -o pw -g pw -m 0700 /app/data +USER 1000:1000 ENV PW_SERVE_HOST=0.0.0.0 EXPOSE 8770 CMD ["pworkers", "serve"] diff --git a/docker-compose.yml b/docker-compose.yml index 7c74388..1330724 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,17 +11,20 @@ # Docker Engine (20.10+) `host.docker.internal` now resolves via the `extra_hosts` entry below. # Running `pworkers` directly on the host with only SearXNG in Docker is still the simplest setup. # -# Note (Linux only): the image has no USER, so it runs as root by default — harmless on Docker -# Desktop (its VirtioFS/gRPC-FUSE layer remaps bind-mount ownership to your host user), but on -# plain Linux Docker Engine a bind mount does NOT remap UIDs, so container root would write -# root-owned (and for 0600 credential files, root-only-readable) files into your shared -# ~/.passiveworkers, locking out your host-native `pworkers` CLI. PW_UID/PW_GID below default to 1000 -# (the common first-user UID on single-user Linux boxes) — override them with `id -u`/`id -g` if -# yours differ, e.g.: PW_UID=$(id -u) PW_GID=$(id -g) docker compose up -d research-desk +# Note (Linux only): the image runs as the unprivileged user `pw` (UID/GID 1000), never root. On +# Docker Desktop that's moot (its VirtioFS/gRPC-FUSE layer remaps bind-mount ownership to your host +# user), but on plain Linux Docker Engine a bind mount does NOT remap UIDs, so the container's UID is +# what owns (and, for 0600 credential files, alone can read) the files it writes into your shared +# ~/.passiveworkers. PW_UID/PW_GID below default to 1000 (the common first-user UID on single-user +# Linux boxes) — override them with `id -u`/`id -g` if yours differ, so your host-native `pworkers` +# CLI can still read them, e.g.: PW_UID=$(id -u) PW_GID=$(id -g) docker compose up -d research-desk +# +# Images are pinned by digest (tag kept for readability). Refresh a pin with +# docker buildx imagetools inspect : → the top-level "Digest:" line services: searxng: - image: searxng/searxng:latest + image: searxng/searxng:latest@sha256:85492ba800a861e3e8ff6014c430e81369fe6ca766a9990595f616d1ce95b272 container_name: pw-searxng ports: - "127.0.0.1:8080:8080" @@ -78,6 +81,9 @@ services: - PW_PORT=${PW_PORT:-8088} - PW_DB=/app/data/coordinator.db volumes: + # a fresh volume inherits the image's pw-owned /app/data. A volume created by an older, + # root-running image stays root-owned — hand it over once before upgrading: + # docker compose run --rm --user root --entrypoint chown coordinator -R pw:pw /app/data - pw-coordinator-db:/app/data restart: unless-stopped