From fda96565ad8269aedff82e807b12a27751d4498b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Wed, 16 Sep 2026 23:43:01 +0200 Subject: [PATCH] fix: [outline] align datastore ownership with the application user Outline runs as an unprivileged user in the image (``nodejs`` since 1.10.0, ``root`` up to 1.6.1) while the datastore is provisioned by ``root``. Without realignment the application cannot write its ``uploads``, ``public`` and ``avatars`` buckets, so every attachment upload fails with "Permission denied writing to ... Check the host machine file system permissions". This was seen on elabore.coop after the 1.6.1 to 1.10.0 upgrade. The ``init`` hook now reads the image's ``Config.User`` and chowns the datastore to that user, skipping root-based images. It is idempotent and version-agnostic: the realignment also covers buckets added by future Outline versions. --- outline/README.org | 16 ++++++++++++++++ outline/hooks/init | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/outline/README.org b/outline/README.org index 9f2a0c5..ee958fe 100644 --- a/outline/README.org +++ b/outline/README.org @@ -64,6 +64,22 @@ there is no drift, and blocks the deployment (=exit 1=) if the realignment fails, so the problem is visible at deploy time instead of as a cryptic crash-loop. +* Datastore ownership alignment + +The =init= hook aligns the ownership of the service datastore with +the user the Outline container runs as. Since 1.10.0 the image runs +as the unprivileged =nodejs= user (older images, up to 1.6.1, ran as +=root=), while the datastore is provisioned by =root=. Without +realignment the application cannot write its =uploads=, =public= and +=avatars= buckets and every attachment upload fails with "Permission +denied writing to ... Check the host machine file system +permissions". This was seen on 2026-09-11 on elabore.coop after the +1.6.1 to 1.10.0 upgrade, on every existing datastore. + +The hook reads the user from the image's =Config.User=, so it stays +version-agnostic: images running as =root= are left untouched, and +re-running the hook on an already aligned datastore is a no-op. + * Building a new image We use the official image with an added patch due to 2 bugs: diff --git a/outline/hooks/init b/outline/hooks/init index cd8413b..d00df11 100755 --- a/outline/hooks/init +++ b/outline/hooks/init @@ -71,4 +71,42 @@ $SERVICE_NAME: #DEBUG: \"http\" " +## The datastore is bind-mounted into the container. Outline runs as +## an unprivileged user (image Config.User: root up to 1.6.1, "nodejs" +## since 1.10.0) and must write its uploads, public and avatars +## buckets. Provisioned by root, the datastore is not writable by +## that user and every upload fails with "Permission denied writing +## to ... Check the host machine file system permissions". Align the +## datastore ownership with the image user; skip images running as +## root. See README.org, "Datastore ownership alignment". +app_user= +if [ -n "$DOCKER_BASE_IMAGE" ]; then + app_user=$(docker image inspect "$DOCKER_BASE_IMAGE" \ + --format '{{.Config.User}}') || exit 1 +fi + +case "$app_user" in + ""|0|0:0|root) + ## image runs as root: nothing to align + ;; + *:*) + uid="${app_user%%:*}" + gid="${app_user#*:}" + ;; + *) + uid_gid=($(docker_get_uid_gid "$SERVICE_NAME" "$app_user" "$app_user")) || exit 1 + uid="${uid_gid[0]}" + gid="${uid_gid[1]}" + ;; +esac + +if [ -n "${uid:-}" ]; then + mkdir -p "$SERVICE_DATASTORE" + chown -R "$uid:$gid" "$SERVICE_DATASTORE" || { + err "Failed to align datastore ownership on '$uid:$gid'." + exit 1 + } + info "Datastore ownership aligned on '$uid:$gid'." +fi +