From 485c8fa5381d99232497da209ac552a5047795b5 Mon Sep 17 00:00:00 2001 From: Claude Opus 5 Date: Sat, 1 Aug 2026 15:12:06 +0200 Subject: [PATCH] #303 follow-up: the fonts volume must mount node-owned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found on the real deploy, not in any test: `/data/fonts` in the running api container was `root:root` and the non-root `node` user could not write to it. Every upload would have failed with EACCES at runtime while the api reported ready. The api Dockerfile already explains the mechanism for uploads and plugins — Docker copies an image directory's ownership into a fresh named volume on first mount — and pre-creates them chowned. #303 added `CUSTOM_FONTS_DIR` to the ENV but not to that mkdir/chown line. Adds a CI fence so it cannot recur: every `/data/…` path the api image defaults to must also appear in the mkdir AND the chown. Verified against the actual defect — removing `/data/fonts` from the chown makes it fail. --- .gitea/workflows/ci.yml | 18 ++++++++++++++++++ apps/api/Dockerfile | 5 +++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index bc66c81..fb723ad 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -107,6 +107,24 @@ jobs: exit 1 fi + # A fresh named volume inherits the ownership of the image directory it + # is mounted over. Every /data/… path the api image defaults to must + # therefore be pre-created AND chowned to `node`, or the non-root user + # cannot write to it — found on a real deploy in #303, where the env + # entry was added but the mkdir/chown line was not. + - name: api image pre-creates its data directories node-owned + run: | + set -euo pipefail + dirs=$(grep -oE '[A-Z_]+_DIR=/data/[a-z]+' apps/api/Dockerfile | cut -d= -f2 | sort -u) + bad=0 + for d in $dirs; do + grep -q "mkdir -p .*$d" apps/api/Dockerfile || { + echo "$d is not pre-created in apps/api/Dockerfile"; bad=1; } + grep -q "chown -R node:node .*$d" apps/api/Dockerfile || { + echo "$d is not chowned to node in apps/api/Dockerfile"; bad=1; } + done + exit "$bad" + - name: Set up pnpm uses: pnpm/action-setup@v4 diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index 880538e..a04e0c6 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -34,13 +34,14 @@ WORKDIR /app COPY --from=build --chown=node:node /out /app # Generate the Prisma client for this image's platform. RUN node node_modules/prisma/build/index.js generate -# A fresh named volume mounted at /data/uploads or /data/plugins is created +# A fresh named volume mounted at /data/uploads, /data/plugins or /data/fonts +# is created # root-owned; pre-creating them here (Docker copies an image directory's # ownership into a new volume on first mount) lets the non-root `node` user # write to them. /data/backups is mounted read-only here, but pre-creating it # node-owned keeps the shared `backups` volume writable for the backup # sidecar even when the api container is the one that initializes it. -RUN mkdir -p /data/uploads /data/plugins /data/secrets /data/backups && chown -R node:node /data/uploads /data/plugins /data/secrets /data/backups +RUN mkdir -p /data/uploads /data/plugins /data/fonts /data/secrets /data/backups && chown -R node:node /data/uploads /data/plugins /data/fonts /data/secrets /data/backups USER node EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ -- 2.45.2