Let the image run as a user that is not root

Nothing here needs root at run time, and a platform that chooses the uid
itself and drops every capability could not start it. Three things were
in the way, found by running it as uid 568 with nothing granted:

  frankenphp     carried cap_net_bind_service as a file capability, and
                 a process without that capability may not execute such
                 a binary at all. Removed: in a container the
                 unprivileged port range starts at 0 anyway.
  var/           cache, log and share were root's. Writable by anyone
                 now, since the uid is not known when the image is built.
  /data, /config root's as well, so the hub could not open its database
                 on a fresh volume. Same answer.

The entrypoint's setfacl step is skipped for a non-root user, who can
grant nothing and has no name for whoami to print.

Installs that run as root, which is every compose file here, are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Paul LütznerandClaude Opus 5.5 committed 2026-10-04 10:06:02 +02:00
1 parent 889aeec925
commit 1ff8c81f4b
2 files changed
+42 -5

No files matched your search

+31 -1
View File
@@ -100,6 +100,28 @@ COPY --link frankenphp/Caddyfile /etc/frankenphp/Caddyfile
COPY --link --from=mercure_upstream /usr/bin/caddy /usr/local/bin/mercure
COPY --link --from=mercure_upstream /etc/caddy/Caddyfile /etc/mercure/Caddyfile
# Runnable as a user that is not root, and not any particular one.
#
# Nothing here needs root at run time, and a platform that picks the uid itself
# (the TrueNAS catalogue runs apps as 568 by default, with every capability
# dropped) found two things in the way before PHP ever started:
#
# - The FrankenPHP binary carries cap_net_bind_service as a FILE capability.
# A process that does not hold that capability may not execute such a file
# at all: "exec: frankenphp: Operation not permitted". It is there so a
# non-root user can bind port 80 on a bare host; in a container the
# unprivileged port range starts at 0, so it buys nothing and costs the
# start. Root keeps binding 80 exactly as before.
# - /data and /config, where the web server and the hub keep their state,
# belong to root. A new volume mounted there copies the directory's
# permissions, so the hub died on "open /data/caddy/mercure.db: permission
# denied". Writable by anyone, because the uid is not known here; there is
# one tenant in this container.
RUN set -eux; \
setcap -r /usr/local/bin/frankenphp; \
mkdir -p /data/caddy /config/caddy; \
chmod -R a+rwX /data /config
ENTRYPOINT ["docker-entrypoint"]
# /healthz, not Caddy's metrics port. The old probe answered as soon as the web
@@ -195,4 +217,12 @@ RUN set -eux; \
composer run-script --no-dev post-install-cmd; \
php bin/console tailwind:build --minify; \
php bin/console asset-map:compile; \
chmod +x bin/console; sync;
chmod +x bin/console; \
# The directories PHP writes at run time, for whichever user that turns out
# to be — see the note on running without root in the base stage. var/ itself
# as well, not recursively: the default secrets directory is created in it
# on first start.
mkdir -p var/share; \
chmod a+rwx var; \
chmod -R a+rwX var/cache var/log var/share; \
sync;
+11 -4
View File
@@ -257,11 +257,18 @@ if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
# TrueNAS layout set off on it at once.
mkdir -p var/cache var/log
if ! setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var/cache var/log 2>/dev/null; then
echo 'Note: POSIX ACLs are not supported on this filesystem; skipping setfacl for var/cache and var/log.'
fi
# Root only. As any other user there is nobody to grant anything to — the
# process can only change ACLs on what it owns, and a uid a platform picked
# has no name for `whoami` to answer with ("cannot find name for user ID
# 568", twice, on every start). The image makes both directories writable
# for that case; see the Dockerfile.
if [ "$(id -u)" = '0' ]; then
if ! setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var/cache var/log 2>/dev/null; then
echo 'Note: POSIX ACLs are not supported on this filesystem; skipping setfacl for var/cache and var/log.'
fi
setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var/cache var/log 2>/dev/null || true
setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var/cache var/log 2>/dev/null || true
fi
fi
exec docker-php-entrypoint "$@"