Skip to content

fix(docker): pin service UIDs and repair volume ownership on upgrade - #333

Open
chrip wants to merge 2 commits into
mainfrom
fix/314-service-uid-pinning
Open

chrip wants to merge 2 commits into
mainfrom
fix/314-service-uid-pinning

Conversation

@chrip

@chrip chrip commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Upgrading the standalone image from v9.3.2 to v9.3.3 with an existing Postgres volume puts the container in a restart loop:

Error: Config owner (postgres:102) and data owner (rabbitmq:103) do not
match, and config owner is not root

The service accounts get their UIDs allocated by the package installs, in package-configuration order, so the package set decides them. Adding --no-install-recommends in v9.3.3 dropped dbus (messagebus) from that layer and everything after it slid down one:

v9.3.2: messagebus 101 redis 102 postgres 103 rabbitmq 104 ds 105
v9.3.3: redis 101 postgres 102 rabbitmq 103 messagebus 104 ds 105

A datadir written by v9.3.2 is therefore owned by a UID that resolves to rabbitmq in v9.3.3, postgresql-common refuses to start on the mismatch, and the entrypoint dies under set -e before nginx. ds kept 105 only because messagebus moved from ahead of postgres to behind it, so the document volume escaped by coincidence, not by design.

Three parts:

  • entrypoint.sh chowns the state directories it owns when, and only when, the current owner differs, before starting anything. This is what rescues volumes that already exist, and it also covers a bind mount arriving owned by something else. $DATA_DIR/.private stays root-owned, since the recursive chown would otherwise hand the persisted secrets to ds.

  • The Dockerfile pins the UIDs/GIDs ahead of the installs, so a future packaging change cannot renumber them again. The pinned values are the ones v9.3.3 already shipped, which makes this a no-op for volumes written by v9.3.3 or later; adduser/useradd in the package postinsts (and the .deb's own, for ds) are no-ops when the account exists.

  • A new build.yml job boots the last released image against persistent volumes and then boots the image built here on the same volumes, asserting a row seeded before the upgrade is still readable after it. The e2e job only ever sees empty volumes, which is why this shipped green. Skipped on tag pushes, where :latest is already the image under test.

Fixes #314

Assisted-by: ClaudeCode:claude-opus-5

Upgrading the standalone image from v9.3.2 to v9.3.3 with an existing
Postgres volume puts the container in a restart loop:

  Error: Config owner (postgres:102) and data owner (rabbitmq:103) do not
  match, and config owner is not root

The service accounts get their UIDs allocated by the package installs, in
package-configuration order, so the package set decides them. Adding
--no-install-recommends in v9.3.3 dropped dbus (`messagebus`) from that
layer and everything after it slid down one:

  v9.3.2:  messagebus 101  redis 102  postgres 103  rabbitmq 104  ds 105
  v9.3.3:  redis 101  postgres 102  rabbitmq 103  messagebus 104  ds 105

A datadir written by v9.3.2 is therefore owned by a UID that resolves to
`rabbitmq` in v9.3.3, postgresql-common refuses to start on the mismatch,
and the entrypoint dies under `set -e` before nginx. `ds` kept 105 only
because messagebus moved from ahead of postgres to behind it, so the
document volume escaped by coincidence, not by design.

Three parts:

- entrypoint.sh chowns the state directories it owns when, and only when,
  the current owner differs, before starting anything. This is what
  rescues volumes that already exist, and it also covers a bind mount
  arriving owned by something else. $DATA_DIR/.private stays root-owned,
  since the recursive chown would otherwise hand the persisted secrets to
  ds.

- The Dockerfile pins the UIDs/GIDs ahead of the installs, so a future
  packaging change cannot renumber them again. The pinned values are the
  ones v9.3.3 already shipped, which makes this a no-op for volumes
  written by v9.3.3 or later; adduser/useradd in the package postinsts
  (and the .deb's own, for `ds`) are no-ops when the account exists.

- A new build.yml job boots the last released image against persistent
  volumes and then boots the image built here on the same volumes,
  asserting a row seeded before the upgrade is still readable after it.
  The e2e job only ever sees empty volumes, which is why this shipped
  green. Skipped on tag pushes, where :latest is already the image under
  test.

Fixes #314

Assisted-by: ClaudeCode:claude-opus-5
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Christoph Schaefer <christoph.schaefer@nextcloud.com>
@eelcowijbrands

eelcowijbrands commented Oct 2, 2026 •

Copy link
Copy Markdown

Confirming from the reporter side of #335: my setup bind-mounts /var/lib/euro-office/documentserver in addition to $DATA_DIR (host: ~/eurooffice/data:/var/lib/euro-office/documentserver), and on 9.3.4 Build 37 every upload failed with receiveTask Error: EACCES: permission denied, mkdir '/var/lib/euro-office/documentserver/App_Data' until I ran chown -R ds:ds on that path inside the running container.
So +1 to @junkerderprovinz's note above: adding /var/lib/euro-office/documentserver to the ensure_owner ds list (or covering it the same way as $DATA_DIR) would be needed for this PR to fully fix both reports. Happy to test the branch against my volume layout once it's updated — it's a two-container, two-bind-mount setup, which is probably a decent regression case for the chown logic anyway.

The converter creates App_Data under /var/lib/euro-office/documentserver,
and that path is commonly mounted alongside $DATA_DIR. A bind mount there
arrives owned by whatever the host created it with, so every upload fails
with EACCES on mkdir App_Data until it is chowned to ds by hand (#335).

Add it to the ds ensure_owner list as $EO_LIB. The upgrade job now mounts
it as a third volume, hands both ds volumes to a foreign owner before
booting the built image, and asserts they come back owned by ds.

Assisted-by: ClaudeCode:claude-opus-5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Christoph Schaefer <christoph.schaefer@nextcloud.com>
@chrip

chrip commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @junkerderprovinz and @eelcowijbrands, /var/lib/euro-office/documentserver is now in the ensure_owner ds list and the upgrade job checks it comes back owned by ds. @eelcowijbrands, could you test the branch against your two-bind-mount setup?

@eelcowijbrands

Copy link
Copy Markdown

Happy to test the branch against my two-bind-mount setup, but docker pull ghcr.io/euro-office/documentserver:pr-333 returns not found — so the PR image doesn't appear to be published publicly. I've backed up and torn down my previous :latest container, so I'm ready to go as soon as there's an image to pull. Is there a public tag or artifact for this branch, or would you prefer I wait for the next :nightly after merge? (For reference: my setup is ~/eurooffice/data:/var/lib/euro-office/documentserver + ~/eurooffice/private:/var/www/euro-office/Data, both currently fresh and owned by my host user, i.e. the exact #335 reproduction case.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

Container stuck in restart loop after upgrading from 9.3.2 to 9.3.3 (PostgreSQL UID mismatch)

2 participants