From 7c572f1f13aee019eee2b9df8851336d91dfc242 Mon Sep 17 00:00:00 2001 From: Brian Gebel Date: Tue, 10 Mar 2026 23:12:22 -0700 Subject: [PATCH 1/4] fix: chown venv to PUID:PGID so ComfyUI Manager can install deps The venv is created as root during build. When running as PUID:PGID, ComfyUI Manager's `uv pip install` fails with permission denied when trying to write to site-packages. Adding recursive chown of .venv in the entrypoint (matching the existing pattern for /app/ComfyUI). Co-Authored-By: Claude Opus 4.6 --- services/comfy/core/entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/services/comfy/core/entrypoint.sh b/services/comfy/core/entrypoint.sh index cde197f..967ed1e 100755 --- a/services/comfy/core/entrypoint.sh +++ b/services/comfy/core/entrypoint.sh @@ -56,6 +56,7 @@ fi # Set ownership of application root directories chown "$PUID:$PGID" /app /app/ComfyUI +chown -R "$PUID:$PGID" /app/.venv # Set ownership of immediate subdirectories (handles volume mount points) # Non-recursive for performance — avoids traversing large model directories From ff21c2a595d20cf49c55384d9b982fbd6f8ecc79 Mon Sep 17 00:00:00 2001 From: Brian Gebel Date: Tue, 10 Mar 2026 23:26:53 -0700 Subject: [PATCH 2/4] fix: build-time user creation, writable venv, and non-root K8s support Create the comfy user (UID 1000) at build time and chown all files to comfy:comfy so they are not root-owned. This is a roll-forward from the pattern used prior to the gosu refactor. Changes: - Create comfy user (APP_UID/APP_GID build args, default 1000) at build time instead of dynamically in entrypoint - COPY --from=builder --chown=comfy:comfy so all files owned by comfy - chmod -R a+w on venv site-packages so ComfyUI Manager can install custom node deps at runtime regardless of the effective UID - Entrypoint detects non-root and execs directly (K8s fast path) - Root-mode gosu path unchanged for Docker Compose PUID/PGID users Deployment modes (no breaking changes): - Docker Compose: starts as root, PUID/PGID + gosu works as before - Docker Compose (default): gosu drops to 1000, files already owned - K8s: securityContext.runAsUser overrides, entrypoint skips gosu Co-Authored-By: Claude Opus 4.6 --- services/comfy/core/dockerfile.comfy.core | 27 ++++++++++++++++------- services/comfy/core/entrypoint.sh | 19 +++++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/services/comfy/core/dockerfile.comfy.core b/services/comfy/core/dockerfile.comfy.core index d6459b8..9f03124 100644 --- a/services/comfy/core/dockerfile.comfy.core +++ b/services/comfy/core/dockerfile.comfy.core @@ -39,12 +39,15 @@ RUN touch /var/mail/ubuntu && \ chown ubuntu /var/mail/ubuntu && \ userdel -r ubuntu -# Create app directory (user/group created at runtime by entrypoint) -RUN mkdir -p /app +# Create non-root app user at build time +ARG APP_UID=1000 +ARG APP_GID=1000 +RUN groupadd -g $APP_GID comfy && \ + useradd -u $APP_UID -g $APP_GID -d /app -s /bin/bash -M comfy && \ + mkdir -p /app -# Copy the pre-built environment from builder -# Docker COPY preserves the "world-readable" permissions created by umask -COPY --from=builder /app /app +# Copy the pre-built environment from builder, owned by comfy +COPY --from=builder --chown=comfy:comfy /app /app WORKDIR /app/ComfyUI @@ -54,11 +57,17 @@ ENV PATH="$VENV_PATH/bin:$PATH" ENV PYTHONPATH="/app/ComfyUI" # Copy startup scripts to app root -COPY startup.sh entrypoint.sh /app/ +COPY --chown=comfy:comfy startup.sh entrypoint.sh /app/ # Make scripts executable (fast/cheap operation on single files) RUN chmod +x /app/startup.sh /app/entrypoint.sh +# Make venv site-packages writable by any user so ComfyUI Manager can +# install custom node deps at runtime regardless of the effective UID +# (Docker Compose PUID/PGID, K8s securityContext.runAsUser, etc.) +RUN chmod -R a+w /app/.venv/lib/python3.12/site-packages && \ + chmod a+w /app/.venv/lib/python3.12 + # Environment variables ARG CLI_ARGS="" ARG COMFY_PORT=8188 @@ -67,6 +76,8 @@ ENV COMFY_PORT=$COMFY_PORT EXPOSE $COMFY_PORT -# Entrypoint runs as root, drops privileges to PUID:PGID internally via gosu +# Entrypoint runs as root by default for Docker Compose PUID/PGID support. +# K8s users override with securityContext.runAsUser — the entrypoint detects +# non-root and skips gosu, executing directly as the assigned UID. ENTRYPOINT ["/app/entrypoint.sh"] -CMD ["/app/startup.sh"] \ No newline at end of file +CMD ["/app/startup.sh"] diff --git a/services/comfy/core/entrypoint.sh b/services/comfy/core/entrypoint.sh index 967ed1e..aa2719a 100755 --- a/services/comfy/core/entrypoint.sh +++ b/services/comfy/core/entrypoint.sh @@ -1,6 +1,24 @@ #!/bin/bash set -e +# ============================================================================= +# Root Detection +# ============================================================================= + +# When running as non-root (K8s securityContext.runAsUser), activate the +# venv and exec directly — no privilege management needed. +if [ "$(id -u)" -ne 0 ]; then + echo "Starting as non-root UID:GID = $(id -u):$(id -g)" + source /app/.venv/bin/activate + exec "$@" +fi + +# ============================================================================= +# Root-mode: Dynamic User Setup (Docker/Compose PUID/PGID support) +# ============================================================================= +# Default path when running as root (Docker/Compose). Set PUID/PGID +# env vars to control the runtime user identity. + # Default UID/GID values for backward compatibility PUID=${PUID:-1000} PGID=${PGID:-1000} @@ -56,7 +74,6 @@ fi # Set ownership of application root directories chown "$PUID:$PGID" /app /app/ComfyUI -chown -R "$PUID:$PGID" /app/.venv # Set ownership of immediate subdirectories (handles volume mount points) # Non-recursive for performance — avoids traversing large model directories From 2e94534925b9d906623118afaf99241f0c9f0f0d Mon Sep 17 00:00:00 2001 From: Brian Gebel Date: Tue, 10 Mar 2026 23:43:38 -0700 Subject: [PATCH 3/4] docs: update documentation for non-root entrypoint and K8s support Document the dual entrypoint paths (root/gosu for Docker Compose, non-root for Kubernetes securityContext), build-time comfy user ownership, and world-writable site-packages for ComfyUI Manager. Co-Authored-By: Claude Opus 4.6 --- README.md | 17 +++++++++++++-- docs/user-guides/data.md | 15 +++++++++++--- docs/user-guides/running.md | 41 +++++++++++++++++++++++++++---------- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 37989cf..06cf163 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ A complete containerized deployment of ComfyUI with GPU acceleration, flexible d - **⚡ Performance Optimized**: SageAttention for 2-3x faster attention computation - **🔧 Extensible**: Custom node support via volume mounts - **🔄 CI/CD Ready**: Automated builds, weekly dependency updates -- **🔒 Security**: API/Swarm/K8s ready with arbitrary user support +- **🔒 Security**: Runs as non-root by default, supports Docker Compose PUID/PGID and Kubernetes securityContext --- @@ -184,8 +184,21 @@ COMFY_OUTPUT_PATH=./data/output # Override output directory PUID=$(id -u) PGID=$(id -g) docker compose up -d # from within an examples/ directory ``` +### Kubernetes Deployment + +The images support Kubernetes natively via `securityContext.runAsUser`. When the entrypoint detects a non-root UID, it skips the gosu/PUID/PGID logic and executes directly: + +```yaml +securityContext: + runAsUser: 3000 + runAsGroup: 3000 + fsGroup: 3000 +``` + +The Python virtual environment's `site-packages` directory is world-writable at build time, so ComfyUI Manager can install custom node dependencies regardless of the runtime UID. + **For complete configuration options, see:** -- [Running Containers Guide](docs/user-guides/running.md) - Environment variables and Docker Compose +- [Running Containers Guide](docs/user-guides/running.md) - Environment variables, Docker Compose, and Kubernetes - [Performance Tuning Guide](docs/user-guides/performance.md) - CLI arguments and optimization --- diff --git a/docs/user-guides/data.md b/docs/user-guides/data.md index 97da9fb..e2a570a 100644 --- a/docs/user-guides/data.md +++ b/docs/user-guides/data.md @@ -142,12 +142,21 @@ volumes: ## File Permissions -Data directories use the user/group ID specified by `PUID` and `PGID` environment variables (default: 1000:1000). +### Build-Time Ownership -**To set ownership:** +All files under `/app` (including ComfyUI and the Python virtual environment) are owned by a `comfy` user (UID 1000, GID 1000) at build time. The venv `site-packages` directory is world-writable (`a+w`) so ComfyUI Manager can install custom node Python dependencies at runtime regardless of the effective UID. + +### Runtime Ownership + +Files created in mounted volumes are owned by the runtime user: + +- **Docker Compose**: Set `PUID` and `PGID` environment variables to match your host user (default: 1000:1000). The entrypoint uses `gosu` to run as that UID/GID. +- **Kubernetes**: Use `securityContext.runAsUser` and `fsGroup` to control file ownership. No PUID/PGID env vars needed. + +**To set ownership (Docker Compose):** ```bash # Use your user ID -id # Shows your PUID and PGID +id # Shows your UID and GID # Set in .env file PUID=1000 diff --git a/docs/user-guides/running.md b/docs/user-guides/running.md index 0567bcf..3ea1884 100644 --- a/docs/user-guides/running.md +++ b/docs/user-guides/running.md @@ -131,15 +131,13 @@ COMFY_IMAGE=custom:latest # Override Docker image See [Performance Tuning](performance.md) for CLI_ARGS options. -## User ID / Group ID (PUID/PGID) +## User Identity -Run the container with your host user's UID/GID to ensure files created in mounted volumes have correct ownership. +The entrypoint supports two modes for controlling the runtime user identity, selected automatically based on how the container starts. -### Why Use PUID/PGID? +### Docker Compose: PUID/PGID (Root Entrypoint) -When the container creates files (outputs, caches, etc.) in mounted volumes, those files are owned by the container's runtime user. Setting PUID/PGID to match your host user prevents permission issues. - -### Usage +When the container starts as root (the default for Docker Compose), the entrypoint uses `gosu` to create and drop privileges to the specified PUID/PGID. This ensures files in mounted volumes have correct ownership. ```bash # Method 1: Inline environment variables (from within your example directory) @@ -150,18 +148,39 @@ echo "PUID=$(id -u)" >> .env echo "PGID=$(id -g)" >> .env docker compose up -d -# Method 3: Specific UID/GID (e.g., shared server) +# Method 3: Specific UID/GID (e.g., shared NAS server) PUID=3000 PGID=3000 docker compose up -d ``` -### Defaults - If PUID/PGID are not specified, the container defaults to UID 1000 and GID 1000, which matches the first non-root user on most Linux systems. +### Kubernetes: securityContext (Non-Root Entrypoint) + +When the container starts as a non-root user (e.g., via Kubernetes `securityContext.runAsUser`), the entrypoint detects this and skips all gosu/PUID/PGID logic. It activates the Python virtual environment and executes directly as the assigned UID. + +```yaml +# Kubernetes Pod spec example +spec: + securityContext: + fsGroup: 3000 + containers: + - name: comfyui + image: ghcr.io/pixeloven/comfyui/core:cuda-latest + securityContext: + runAsUser: 3000 + runAsGroup: 3000 +``` + +No PUID/PGID environment variables are needed in this mode — the UID/GID comes from the Kubernetes security context. The `fsGroup` setting ensures volume mounts are group-accessible. + +### File Ownership + +At build time, all application files under `/app` are owned by a `comfy` user (UID 1000). The venv `site-packages` directory is world-writable so that ComfyUI Manager can install custom node dependencies at runtime regardless of the effective UID. + ### Verification ```bash -# Check container user +# Docker Compose: check container user docker exec comfyui-core-gpu id # Expected: uid=3000(comfy) gid=3000(comfy) groups=3000(comfy) @@ -246,7 +265,7 @@ docker compose ps -a # Fix ownership (use your PUID:PGID) sudo chown -R $USER:$USER ./data -# Or set in .env +# Or set PUID/PGID in .env (Docker Compose only) PUID=1000 PGID=1000 ``` From c8cf3bafbb9bc2187fde8cef15b8fda88b90f8a8 Mon Sep 17 00:00:00 2001 From: Brian Gebel Date: Wed, 11 Mar 2026 06:39:50 -0700 Subject: [PATCH 4/4] fix: set HOME and XDG_CACHE_HOME in final stage for non-root support XDG_CACHE_HOME was only set in the builder stage but not carried to the final image. When running as a non-root UID (e.g., K8s securityContext), uv/pip fail trying to create cache at /.cache/ which isn't writable. HOME also defaults to / for unknown UIDs. Co-Authored-By: Claude Opus 4.6 --- services/comfy/core/dockerfile.comfy.core | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/comfy/core/dockerfile.comfy.core b/services/comfy/core/dockerfile.comfy.core index 9f03124..a4a6fbd 100644 --- a/services/comfy/core/dockerfile.comfy.core +++ b/services/comfy/core/dockerfile.comfy.core @@ -55,6 +55,8 @@ WORKDIR /app/ComfyUI ENV VENV_PATH=/app/.venv ENV PATH="$VENV_PATH/bin:$PATH" ENV PYTHONPATH="/app/ComfyUI" +ENV HOME=/app +ENV XDG_CACHE_HOME=/app/.cache # Copy startup scripts to app root COPY --chown=comfy:comfy startup.sh entrypoint.sh /app/