Skip to content

Data volume is never chowned to the ds user: EACCES for adminpanel/docservice on a bind-mounted Data directory #335

Description

@junkerderprovinz

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

  1. Create an empty host directory not owned by the image's ds uid, for example mkdir data && chown nobody:users data && chmod 755 data.
  2. Run the standalone image with -v $PWD/data:/var/www/euro-office/Data and -e ADMINPANEL_ENABLED=true.
  3. 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:

  1. Uncomment the chown -R ds:ds in standalone.bake.Dockerfile, so the image ships with correct ownership for the non-mounted case.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    📄 To do

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions