Skip to content

docker arbitrary user support via PUID and PGID#9633

Open
kon-foo wants to merge 5 commits intopgadmin-org:masterfrom
kon-foo:feature/docker-arbitrary-user-support
Open

docker arbitrary user support via PUID and PGID#9633
kon-foo wants to merge 5 commits intopgadmin-org:masterfrom
kon-foo:feature/docker-arbitrary-user-support

Conversation

@kon-foo
Copy link

@kon-foo kon-foo commented Feb 13, 2026

This PR adds support for PUID and PGID environment variables as popularised by linuxserver.io images. This allows users to control the UID and GID of the process running gunicorn inside the container.

Why

Currently, when running pgAdmin via Docker with bind-mounted volumes, the mounted directories must be owned by the hardcoded 5050 user on the host and inside the container. This creates two issues:

  1. Permission Headaches:
    Users are forced to manually chown directories on the host to 5050 before starting the container. This is cumbersome, especially if running docker as a non-root users and potentially lose access to the mounted dir.

  2. Security Risks:
    The hardcoded 5050 UID might already be in use on the host system. Running the container as this UID could unintentionally grant pgAdmin access to files or resources owned by that UID or vise versa.

Solution

  1. Remove the hardcoded 5050 user, so the container starts then enrypoint.sh script as UID 0 by default.
  2. In the script, default to PUID 5050 and GUID 0 to keep current behaviour when no env vars are set.
  3. If the script runs as "$(id -u)" = "0" , aka no --user flag was used to run the image:
    • ensure a group with PGID exists and reassign the existing pgadmin user to PUID and PGID.
    • chown required files and dirs to said user.
    • use su-exec to drop privileges and run gunicorn as $PUID:$PGID

The check for id=0 makes sure nothing changes for users, that use the --user flag or user: in compose to run the image.

Compatability

Without PUID/PGID set, the behaviour is identical to the current image.

Tests

For each test, I cheked:

# The id of the pgadmin user
docker exec pgadmin-test id pgadmin
# The the gunicorn process runs as that user
docker exec pgadmin-test ps aux | grep gunicorn | grep -v grep
# The permissions of a mounted volume
ls -ln /tmp/pgadmin-test-data/
# Access to the web UI
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/login
# Scenario Key results
1 Default (no PUID/PGID) ✅ Still starts as UID 5050, GID 0
2 PUID=1000 PGID=1000 ✅ UID 1000, GID 1000, vmodeolume owned correctly
3 PUID only ✅ UID custom, GID defaults to 0

Further tests conducted:

# Scenario Key results
4 TLS ✅ TLS still works. Won't work if /certs is mounted as a read-only filesystem, but that is expected.
5 Server JSON import ✅ Imported as root during initialization, no change of permissions needed.

Open Questions:

  • Are there additional paths that need to be chowned? Currently /run/pgadmin, /var/lib/pgadmin, /certs and pgadmin4/config_distro.py are covered.
  • Are there any integration tests to be run?

I have been using this for a couple of weeks now, but I am only using a very small subset of PgAdmin functionality.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added PUID/PGID environment variable support for flexible container user configuration.
    • Enhanced OpenShift compatibility with improved user and permission handling.
  • Chores

    • Updated container runtime configuration and added required system libraries for improved container execution.

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

Walkthrough

The changes modify Docker container setup to add runtime dependencies (libcap, su-exec) and adjust user configuration. The entrypoint script now handles PUID/PGID environment variables for flexible user ID configuration, implements root-aware initialization with group/user reassignment, OpenShift passwd compatibility, and wraps Gunicorn invocations with su-exec wrapper for privilege management.

Changes

Cohort / File(s) Summary
Docker Runtime Configuration
Dockerfile
Added libcap and su-exec to apk dependencies; removed USER 5050 instruction to allow container to run as root (runtime UID configuration deferred to entrypoint).
Entrypoint Initialization
pkg/docker/entrypoint.sh
Introduced PUID/PGID environment variable defaults with root-aware initialization logic. When running as root, creates target group, reassigns pgadmin user UID/GID, and establishes SU_EXEC wrapper. Adds OpenShift compatibility via passwd file handling. Wraps Gunicorn invocations with su-exec wrapper while preserving all existing flags and configurations. Includes ownership adjustment for runtime paths prior to server startup.

Sequence Diagram(s)

sequenceDiagram
    participant Container as Container Runtime
    participant Entrypoint as entrypoint.sh
    participant System as System (groups/users)
    participant Gunicorn

    Container->>Entrypoint: Start (PUID/PGID env vars set)
    Entrypoint->>Entrypoint: Check if running as root
    alt Running as Root
        Entrypoint->>System: Create group with PGID
        Entrypoint->>System: Reassign pgadmin UID/GID
        Entrypoint->>Entrypoint: Set SU_EXEC wrapper
    else Not Root (OpenShift)
        Entrypoint->>Entrypoint: Check /etc/passwd writable
        alt Passwd Writable
            Entrypoint->>System: Append pgadmin user entry
        end
    end
    Entrypoint->>System: Adjust ownership of runtime paths
    Entrypoint->>System: Adjust ownership of config files
    Entrypoint->>Gunicorn: Invoke with SU_EXEC wrapper<br/>(preserving all flags)
    Gunicorn-->>Container: Running
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Optimized Dockerfile #9378: Modifies Dockerfile final runtime setup with similar USER instruction and entrypoint-related behavior changes, addressing comparable containerization flexibility requirements.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding support for arbitrary UID/GID configuration via PUID and PGID environment variables.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into master

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pkg/docker/entrypoint.sh`:
- Around line 199-201: The chown block should avoid errors for missing /certs,
avoid always-recursing /var/lib/pgadmin, and honor a custom config path; update
the block to: check existence of /certs before attempting chown, check current
owner of /var/lib/pgadmin (using stat) and only run chown -R if it differs from
"$PUID:$PGID" to avoid costly repeated recursion, and replace the hardcoded
/pgadmin4/config_distro.py with the PGADMIN_CUSTOM_CONFIG_DISTRO_FILE variable
(if set and the file exists) and chown that file non-recursively; ensure all
chown commands still run only when uid is 0.
🧹 Nitpick comments (3)
pkg/docker/entrypoint.sh (3)

5-19: Consider validating PUID/PGID are numeric before use.

If a user sets PUID=abc, usermod fails silently (line 12 swallows errors), then su-exec abc:0 will fail at the very end of the script with a cryptic error. Fail-fast with a clear message would improve the user experience.

🛡️ Proposed validation
 PUID=${PUID:-5050}
 PGID=${PGID:-0}
+
+# Validate PUID/PGID are numeric
+if ! echo "$PUID" | grep -qE '^[0-9]+$'; then
+    echo "Error: PUID must be a numeric value, got '$PUID'" >&2
+    exit 1
+fi
+if ! echo "$PGID" | grep -qE '^[0-9]+$'; then
+    echo "Error: PGID must be a numeric value, got '$PGID'" >&2
+    exit 1
+fi
 
 if [ "$(id -u)" = "0" ]; then

155-162: DB initialization runs as root before ownership is fixed.

run_pgadmin.py (line 157) and load-servers (line 113) execute as UID 0 when the container starts as root, creating files in /var/lib/pgadmin owned by root. The chown -R at line 199 corrects this, but if the script fails between these points the volume is left root-owned.

An alternative is to move the chown + su-exec wrapping earlier, so that the initialization itself runs as the target user. This would also eliminate the need for the expensive recursive chown.


12-12: Silent usermod failure could mislead operators.

2>/dev/null || true suppresses all diagnostics. If usermod fails for an unexpected reason (e.g., shadow/passwd locked), the script continues and line 16 still prints the intended UID/GID — but the pgadmin user was never actually reassigned. Gunicorn will still launch via su-exec with the numeric UID (so it works), but any files created during init (lines 157–187) will be owned by the old pgadmin UID.

Consider logging the failure:

Proposed change
-    usermod -o -u "$PUID" -g "$PGID" pgadmin 2>/dev/null || true
+    if ! usermod -o -u "$PUID" -g "$PGID" pgadmin 2>/dev/null; then
+        echo "Warning: failed to reassign pgadmin user to UID=$PUID GID=$PGID" >&2
+    fi

Comment on lines +199 to +201
if [ "$(id -u)" = "0" ]; then
chown -R "$PUID:$PGID" /run/pgadmin /var/lib/pgadmin /pgadmin4/config_distro.py /certs
fi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Multiple issues with the chown block.

  1. /certs may not exist when TLS is not enabled, producing a noisy error on every startup for non-TLS users.

  2. chown -R on /var/lib/pgadmin runs on every start and can be extremely slow for volumes with many files (e.g., large session/storage directories). Consider guarding with an ownership check or limiting recursion depth.

  3. Hardcoded /pgadmin4/config_distro.py — Line 58 allows overriding the config distro path via PGADMIN_CUSTOM_CONFIG_DISTRO_FILE. If a custom path is used, this chown won't cover the actual config file.

🔧 Proposed fix addressing all three points
 if [ "$(id -u)" = "0" ]; then
-    chown -R "$PUID:$PGID" /run/pgadmin /var/lib/pgadmin /pgadmin4/config_distro.py /certs
+    chown -R "$PUID:$PGID" /run/pgadmin /var/lib/pgadmin
+    chown "$PUID:$PGID" "${CONFIG_DISTRO_FILE_PATH}"
+    if [ -d /certs ]; then
+        chown -R "$PUID:$PGID" /certs
+    fi
 fi

For the /var/lib/pgadmin performance concern, a common pattern is to only recurse when ownership doesn't match:

if [ "$(stat -c '%u:%g' /var/lib/pgadmin)" != "$PUID:$PGID" ]; then
    chown -R "$PUID:$PGID" /var/lib/pgadmin
fi
🤖 Prompt for AI Agents
In `@pkg/docker/entrypoint.sh` around lines 199 - 201, The chown block should
avoid errors for missing /certs, avoid always-recursing /var/lib/pgadmin, and
honor a custom config path; update the block to: check existence of /certs
before attempting chown, check current owner of /var/lib/pgadmin (using stat)
and only run chown -R if it differs from "$PUID:$PGID" to avoid costly repeated
recursion, and replace the hardcoded /pgadmin4/config_distro.py with the
PGADMIN_CUSTOM_CONFIG_DISTRO_FILE variable (if set and the file exists) and
chown that file non-recursively; ensure all chown commands still run only when
uid is 0.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant