Skip to content

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

Open
chrip wants to merge 1 commit into
mainfrom
fix/314-service-uid-pinning
Open

fix(docker): pin service UIDs and repair volume ownership on upgrade#333
chrip wants to merge 1 commit 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>
@chrip
chrip requested a review from a team as a code owner August 18, 2026 08:45
@chrip
chrip requested review from juliusknorr and removed request for a team August 18, 2026 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 📄 To do

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)

1 participant