Summary
In the standalone image, nothing ever gives the ds service user write access to /var/www/euro-office/Data. The build-time chown -R ds:ds is commented out in standalone.bake.Dockerfile, and entrypoint.sh runs entirely as root, creating everything under $DATA_DIR as root before handing off to supervisord, which then drops every ds-* program to user=ds. On a bind-mounted Data directory (which the image itself tells users to mount, see below) any ds-run service that needs to write its own state under Data fails with EACCES. The reported case is the admin panel, which cannot create runtime.json.
Verified against main at eca0e49acfb6cf14ad47e6a9d31ea367ac5f722d.
Reported error
EACCES: permission denied, open '/var/www/euro-office/documentserver/../Data/runtime.json'
That path resolves to /var/www/euro-office/Data/runtime.json, which is DATA_DIR as defined in entrypoint.sh.
1. The build-time chown is commented out
build/.docker/standalone.bake.Dockerfile#L84-L85:
#RUN mkdir -p /var/lib/${COMPANY_NAME_LOW} #&& \
# chown -R ds:ds /var/www/${COMPANY_NAME_LOW} /var/lib/${COMPANY_NAME_LOW} /var/log/${COMPANY_NAME_LOW}
The whole block is inert, so /var/www/${COMPANY_NAME_LOW} is never chowned at build time. The only chown that survives in the finalubuntu stage is line 74 for /home/ds, added in #216 for the sharp cache. Nothing covers the Data directory.
Adjacent context, not necessarily a bug: line 40 #RUN useradd -r -s /bin/false ds || true is commented out as well. That one looks harmless in practice, since the ds account comes from the .deb postinst instead. Worth noting that this also means the ds uid/gid in the standalone image is whatever the package assigns at build time, not a pinned value, in contrast to orchestrated.bake.Dockerfile, which pins uid/gid 101 explicitly.
2. entrypoint.sh creates Data content as root and never chowns it
build/scripts/standalone/entrypoint.sh runs as root from first line to last, with no privilege drop anywhere and no chown anywhere (grep -n 'chown\|gosu\|setpriv\|runuser\|su -' entrypoint.sh returns nothing). It creates, all as root:
$DATA_DIR/.private (L92-L93), mkdir -p plus chmod 700
$DATA_DIR/.private/jwt_secret (L106-L107), chmod 600
$DATA_DIR/.private/secure_link_secret (L124-L125), chmod 600
$DATA_DIR/wopi_private.key and $DATA_DIR/wopi_public.key when WOPI_ENABLED=true (L139-L147), private key chmod 600
Its last line is exec /usr/bin/supervisord (L551). supervisord.conf sets user=root, and each ds-*.conf sets user=ds (ds-adminpanel.conf, ds-docservice.conf, ds-converter.conf, ds-example.conf, ds-metrics.conf). So the services are the only thing in the chain running as ds, and by then everything under Data is root-owned with 600/700 modes.
Note that L108 of the same script instructs the user to do exactly the thing that triggers the failure:
Mount ${DATA_DIR} as a volume to persist it across restarts.
3. Net effect
With Data bind-mounted, the top-level directory keeps whatever ownership the host or orchestrator created it with. In the reported case that was nobody:users 0755, so ds is neither owner nor in the group and gets read and execute only, no write. Everything entrypoint.sh then creates inside it is root-owned and mode 600/700, which ds can neither read nor write. Any ds-run service that needs its own state under Data fails. The admin panel is the visible case because it writes runtime.json on first use.
This is not specific to any one host platform. It reproduces on any Docker host where the bind-mounted directory does not already happen to be owned by whatever uid/gid the image's ds user resolves to, which is not a value the user can predict from the image since the standalone stage does not pin it.
For comparison, orchestrated.bake.Dockerfile does handle this: it creates ds with a fixed uid/gid, runs chown -R ds:ds over the writable paths, and sets USER ds. The standalone image is the outlier here.
Reproduction
- Create an empty host directory not owned by the image's
ds uid, for example mkdir data && chown nobody:users data && chmod 755 data.
- Run the standalone image with
-v $PWD/data:/var/www/euro-office/Data and -e ADMINPANEL_ENABLED=true.
- Open the admin panel and set the password.
Result: the request fails and the adminpanel log shows the EACCES on .../Data/runtime.json. ls -la in the container shows .private/, and with WOPI_ENABLED=true also wopi_private.key and wopi_public.key, owned by root:root.
Suggested fix
Either of these would close the gap, and doing both is safest:
- Uncomment the
chown -R ds:ds in standalone.bake.Dockerfile, so the image ships with correct ownership for the non-mounted case.
- Add an explicit
chown -R ds:ds "$DATA_DIR" in entrypoint.sh, placed after the secret and key generation and before exec /usr/bin/supervisord. This is the one that also covers the bind-mount case and the files the entrypoint itself creates, which a build-time chown can never reach.
The same reasoning applies to $EO_LOG and to any other path an ds-run service writes to.
Related issues
Possibly the same root cause, or at least adjacent, though none of them name the Data directory or the ownership mechanism: #283 (admin panel settings not persistent), #299 (persistent volume support), #327 (volume access issues, and the reporter there explicitly asks whether the container runs as root).
Note on workaround
I hit this while packaging the image for Unraid. An independent third-party wrapper at https://github.com/junkerderprovinz/euro-office ships an extra supervisor program that fixes ownership under Data at startup, as a stopgap until this is addressed upstream. That is a workaround in packaging, not a fix in the image, and no PR is being proposed here.
Summary
In the standalone image, nothing ever gives the
dsservice user write access to/var/www/euro-office/Data. The build-timechown -R ds:dsis commented out instandalone.bake.Dockerfile, andentrypoint.shruns entirely as root, creating everything under$DATA_DIRas root before handing off to supervisord, which then drops everyds-*program touser=ds. On a bind-mountedDatadirectory (which the image itself tells users to mount, see below) anyds-run service that needs to write its own state underDatafails withEACCES. The reported case is the admin panel, which cannot createruntime.json.Verified against
mainateca0e49acfb6cf14ad47e6a9d31ea367ac5f722d.Reported error
That path resolves to
/var/www/euro-office/Data/runtime.json, which isDATA_DIRas defined inentrypoint.sh.1. The build-time chown is commented out
build/.docker/standalone.bake.Dockerfile#L84-L85:The whole block is inert, so
/var/www/${COMPANY_NAME_LOW}is never chowned at build time. The onlychownthat survives in thefinalubuntustage is line 74 for/home/ds, added in #216 for the sharp cache. Nothing covers the Data directory.Adjacent context, not necessarily a bug: line 40
#RUN useradd -r -s /bin/false ds || trueis commented out as well. That one looks harmless in practice, since thedsaccount comes from the .deb postinst instead. Worth noting that this also means thedsuid/gid in the standalone image is whatever the package assigns at build time, not a pinned value, in contrast toorchestrated.bake.Dockerfile, which pins uid/gid 101 explicitly.2. entrypoint.sh creates Data content as root and never chowns it
build/scripts/standalone/entrypoint.shruns as root from first line to last, with no privilege drop anywhere and nochownanywhere (grep -n 'chown\|gosu\|setpriv\|runuser\|su -' entrypoint.shreturns nothing). It creates, all as root:$DATA_DIR/.private(L92-L93),mkdir -ppluschmod 700$DATA_DIR/.private/jwt_secret(L106-L107),chmod 600$DATA_DIR/.private/secure_link_secret(L124-L125),chmod 600$DATA_DIR/wopi_private.keyand$DATA_DIR/wopi_public.keywhenWOPI_ENABLED=true(L139-L147), private keychmod 600Its last line is
exec /usr/bin/supervisord(L551).supervisord.confsetsuser=root, and eachds-*.confsetsuser=ds(ds-adminpanel.conf,ds-docservice.conf,ds-converter.conf,ds-example.conf,ds-metrics.conf). So the services are the only thing in the chain running asds, and by then everything underDatais root-owned with 600/700 modes.Note that L108 of the same script instructs the user to do exactly the thing that triggers the failure:
3. Net effect
With
Databind-mounted, the top-level directory keeps whatever ownership the host or orchestrator created it with. In the reported case that wasnobody:users0755, sodsis neither owner nor in the group and gets read and execute only, no write. Everythingentrypoint.shthen creates inside it is root-owned and mode 600/700, whichdscan neither read nor write. Anyds-run service that needs its own state underDatafails. The admin panel is the visible case because it writesruntime.jsonon first use.This is not specific to any one host platform. It reproduces on any Docker host where the bind-mounted directory does not already happen to be owned by whatever uid/gid the image's
dsuser resolves to, which is not a value the user can predict from the image since the standalone stage does not pin it.For comparison,
orchestrated.bake.Dockerfiledoes handle this: it createsdswith a fixed uid/gid, runschown -R ds:dsover the writable paths, and setsUSER ds. The standalone image is the outlier here.Reproduction
dsuid, for examplemkdir data && chown nobody:users data && chmod 755 data.-v $PWD/data:/var/www/euro-office/Dataand-e ADMINPANEL_ENABLED=true.Result: the request fails and the adminpanel log shows the
EACCESon.../Data/runtime.json.ls -lain the container shows.private/, and withWOPI_ENABLED=truealsowopi_private.keyandwopi_public.key, owned byroot:root.Suggested fix
Either of these would close the gap, and doing both is safest:
chown -R ds:dsinstandalone.bake.Dockerfile, so the image ships with correct ownership for the non-mounted case.chown -R ds:ds "$DATA_DIR"inentrypoint.sh, placed after the secret and key generation and beforeexec /usr/bin/supervisord. This is the one that also covers the bind-mount case and the files the entrypoint itself creates, which a build-time chown can never reach.The same reasoning applies to
$EO_LOGand to any other path ands-run service writes to.Related issues
Possibly the same root cause, or at least adjacent, though none of them name the
Datadirectory or the ownership mechanism: #283 (admin panel settings not persistent), #299 (persistent volume support), #327 (volume access issues, and the reporter there explicitly asks whether the container runs as root).Note on workaround
I hit this while packaging the image for Unraid. An independent third-party wrapper at https://github.com/junkerderprovinz/euro-office ships an extra supervisor program that fixes ownership under
Dataat startup, as a stopgap until this is addressed upstream. That is a workaround in packaging, not a fix in the image, and no PR is being proposed here.