Skip to content

run --local --ensure-db: PostgreSQL start step doesn't use sudo, silently fails as non-root, misreports as 20s readiness timeout #984

Description

Summary

mxcli run --local --ensure-db times out with PostgreSQL did not become ready at 127.0.0.1:5432 within 20s on a standard non-root devcontainer (Debian/Ubuntu base image, remoteUser: vscode), even though PostgreSQL is correctly installed and the system cluster (pg_lsclusters) exists.

This reproduces on any devcontainer following the pattern mxcli init itself generates for .devcontainer/: a Debian-based image, postgresql + postgresql-client installed, remoteUser set to a non-root user with sudo scoped to (root) only (the default in mcr.microsoft.com/devcontainers/base images).

Root cause (confirmed via strace -f -e trace=execve,connect)

startLocalPostgres invokes:

execve("/usr/sbin/service", ["service", "postgresql", "start"], ...)

without any sudo prefix. Debian's /etc/init.d/postgresql unconditionally calls create_socket_directory on every start invocation (even when a cluster is already registered/stopped), which does chmod/chown on /var/run/postgresql. That requires root:

$ service postgresql start
chmod: changing permissions of '/var/run/postgresql': Operation not permitted

Since this fails silently (exit 1, no output captured, nothing written to /var/log/postgresql/*.log, no process spawned), --ensure-db falls through to its pg_isready polling loop, waits the full 20s, and reports a generic timeout with no indication of the real permission error.

This is distinct from #823 / PR #824: that fix addressed hosts with no working service manager (e.g. Arch), adding a fallback to a user-owned cluster under ~/.mxcli/postgres. Per the changelog for that fix, the fallback deliberately does not trigger when a system cluster already "owns" the requested port — so on Debian/Ubuntu (where pg_ctlcluster/service exist and a system cluster is registered) it stays on the "retained system-cluster path," which uses sudo -u postgres only for the later role/database provisioning step — never for actually starting the service.

Repro

  1. mxcli init in a fresh repo (or the equivalent hand-rolled devcontainer: Debian base image, postgresql+postgresql-client installed, remoteUser non-root with default sudo scope).
  2. Open in the devcontainer (or start a fresh one) so the PostgreSQL cluster is registered but not yet started.
  3. ./mxcli run --local -p <project>.mpr --ensure-db

Expected: PostgreSQL starts and provisioning proceeds.
Actual:

Ensuring database...
  Starting local PostgreSQL...
Error: ensuring database: PostgreSQL did not become ready at 127.0.0.1:5432 within 20s

Suggested fix

When service postgresql start / pg_ctlcluster ... start is attempted and the current process isn't root, prefix with sudo (mirroring the existing sudo -u postgres pattern already used for role/database provisioning on the same "system cluster" path) — or at minimum surface the underlying command's stderr/exit status instead of only reporting the generic 20s readiness timeout, the way the role-creation step already does (creating role "mendix": exit status 1 + the actual sudo error text).

Environment

  • mxcli: nightly build downloaded 2026-08-28 (via SessionStart hook auto-download)
  • Base image: mcr.microsoft.com/devcontainers/base:bookworm
  • PostgreSQL: 15 (Debian package, postgresql + postgresql-client)
  • remoteUser: vscode (default sudoers: vscode ALL=(root) NOPASSWD:ALL)
  • Mendix project version: 11.12.2

Workaround in use

Added a postStartCommand in devcontainer.json that pre-starts the cluster as root on every container start:

"postStartCommand": "sudo pg_ctlcluster 15 main start || true"

This works because once PostgreSQL is already listening, the unprivileged service postgresql start attempt inside --ensure-db still fails internally but no longer matters — the subsequent pg_isready poll succeeds immediately. Also added a sudoers drop-in (vscode ALL=(postgres) NOPASSWD: ALL) so the later sudo -u postgres role/database provisioning step doesn't hit its own separate sudo: a password is required failure (the base image's sudoers rule only covers (root), not (postgres)).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions