From 78651bdf560fae12da1d70e32e902a6e57f694b9 Mon Sep 17 00:00:00 2001 From: Pavdig <101715456+Pavdig@users.noreply.github.com> Date: Sat, 17 Jan 2026 21:51:50 +0100 Subject: [PATCH] Docker: Build Optimization and Enhanced Non-Root/Volumeless Support (#5024) * docker: optimize build layers and enhance permission handling - Pre-created hardcoded dirs in Dockerfile to support volumeless non-root runs. - Enhanced slightly docker-entrypoint.sh with robust volume detection and safer chown logic. - Included legacy 'backups' directory... again. - Added dos2unix to install list. - Updated .dockerignore - Updated comments - Smaller fixes * fix(docker): removed unnecessary comment, and the... *sighs* backups dir, again * Exclude DS_Store everywhere * Exclude tests and all jsconfigs from docker images * Exclude local plugins from docker builds * fix(docker): backups are back... yay xD * feat(docker): add robust healthcheck script - Added `docker/healthcheck.cjs`: A standalone, dependency-free Node.js script for verifying server status. - Updated `Dockerfile`: Added HEALTHCHECK instruction and script copy step. - Features: Auto-detects port from env/config, handles IPv4/IPv6 fallback, auto-retries HTTPS on socket hangup, and sets custom User-Agent. * Fix .dockerignore permission * Revert "feat(docker): add robust healthcheck script" This reverts commit fa634fb08884cdef9245a12271cb9a13b487365f. --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com> --- .dockerignore | 70 +++++++++++++++++++++++++++++-------- Dockerfile | 20 +++++------ docker/docker-entrypoint.sh | 33 +++++++---------- 3 files changed, 78 insertions(+), 45 deletions(-) diff --git a/.dockerignore b/.dockerignore index ccbc33d5b..db0408ffd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,21 +1,61 @@ +# --- Git & CI --- .git .github -.vscode -node_modules -npm-debug.log -readme* -Start.bat -/dist -/backups -cloudflared.exe -access.log -/data -/cache -.DS_Store -/public/scripts/extensions/third-party -/colab -.gemini +.gitignore + +# --- Docker --- +/Dockerfile +/.dockerignore +/docker/docker-compose.yml /docker/config /docker/extensions /docker/data /docker/plugins +/public/scripts/extensions/third-party + +# --- Plugins (keep only package files) --- +/plugins/* +!/plugins/package.json +!/plugins/package-lock.json + +# --- The Folders --- +/backups +/cache +/colab +/data +/dist +/node_modules +/tests + +# --- Sensitive Info --- +**/.env* +**/*.pem +**/certs + +# --- Documentation --- +readme* +*.md +Update-Instructions.txt + +# --- OS & System Junk --- +**/.DS_Store +*.bat +*.cmd +*.exe +start.sh + +# --- Dev Config --- +.editorconfig +.eslintrc.cjs +.eslintrc* +.vscode +**/jsconfig.json +.npmignore +.gemini +replit.nix +.replit +.nomedia + +# -- Logs & Temp --- +*.log +**/tmp diff --git a/Dockerfile b/Dockerfile index 73b5d5de6..999164f40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,8 @@ FROM node:lts-alpine3.23 ARG APP_HOME=/home/node/app # Install system dependencies -# Added su-exec and shadow to support optional PUID/PGID user mapping -RUN apk add --no-cache gcompat tini git git-lfs su-exec shadow +# "Don't rely on the base image for tools; if you call it, you install it." ;) +RUN apk add --no-cache gcompat tini git git-lfs su-exec shadow dos2unix # Create app directory and set ownership WORKDIR ${APP_HOME} @@ -21,28 +21,28 @@ RUN \ echo "*** Install npm packages ***" && \ npm ci --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force -# Create config directory and link config.yaml +# Create config directory and link config.yaml. Added hardcoded dirs(constants.js?) +# that must be present for Non-Root Mode and volumeless docker runs. RUN \ rm -f "config.yaml" || true && \ - ln -s "./config/config.yaml" "config.yaml" || true && \ - mkdir "config" || true -# Set ownership -RUN chown -R node:node config + mkdir -p config data plugins public/scripts/extensions/third-party backups && \ + chown -R node:node config data plugins public/scripts/extensions/third-party backups && \ + ln -s "./config/config.yaml" "config.yaml" # Pre-compile public libraries RUN \ echo "*** Run Webpack ***" && \ node "./docker/build-lib.js" -# Set the entrypoint script +# Set the entrypoint script and cleanup RUN \ echo "*** Cleanup ***" && \ mv "./docker/docker-entrypoint.sh" "./" && \ - rm -rf "./docker" && \ echo "*** Make docker-entrypoint.sh executable ***" && \ chmod +x "./docker-entrypoint.sh" && \ echo "*** Convert line endings to Unix format ***" && \ - dos2unix "./docker-entrypoint.sh" + dos2unix "./docker-entrypoint.sh" && \ + rm -rf "./docker" # Fix extension repos permissions RUN git config --global --add safe.directory "*" diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index a4aa64f0c..b4b744266 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -8,7 +8,7 @@ start_sillytavern() { # Config Check if [ ! -e "config/config.yaml" ]; then echo "Resource not found, copying from defaults: config.yaml" - $PREFIX cp -r "default/config.yaml" "config/config.yaml" + $PREFIX cp "default/config.yaml" "config/config.yaml" fi # Execute postinstall to auto-populate config.yaml with missing values @@ -20,7 +20,7 @@ start_sillytavern() { # Dirs that MUST be present at this point (e.g for volumeless docker runs). # Please update list, if in the future a related perm issue appear. -CORE_DIRS="config data plugins public/scripts/extensions/third-party" +CORE_DIRS="config data plugins public/scripts/extensions/third-party backups" # Mounted Volumes (External) # Parse mounts, handling files vs directories @@ -35,11 +35,7 @@ for mount in $RAW_MOUNTS; do # Performance Safety: If the file is in the root of the app, # we do NOT add the parent (App Root), or we will recursively scan the whole app. - if [ "$PARENT_DIR" = "/home/node/app" ]; then - MOUNTED_DIRS="$MOUNTED_DIRS $mount" - else - MOUNTED_DIRS="$MOUNTED_DIRS $PARENT_DIR" - fi + [ "$PARENT_DIR" != "/home/node/app" ] && MOUNTED_DIRS="$MOUNTED_DIRS $PARENT_DIR" || MOUNTED_DIRS="$MOUNTED_DIRS $mount" else # It is a directory, add it directly MOUNTED_DIRS="$MOUNTED_DIRS $mount" @@ -53,21 +49,19 @@ CHECK_DIRS=$(echo "$CORE_DIRS $MOUNTED_DIRS" | tr ' ' '\n' | sort -u) for dir in $CHECK_DIRS; do if [ ! -e "$dir" ]; then echo "Creating missing directory: $dir" - mkdir -p "$dir" + mkdir -p "$dir" 2>/dev/null || echo "Warning: Could not create $dir" >&2 fi done -# Change permissions only if started as Root(UID 0) and needed. +# Mode Selection if [ "$(id -u)" = "0" ]; then # Check if PUID/PGID variables are provided if [ -n "$PUID" ] && [ -n "$PGID" ]; then - TARGET_UID=$PUID - TARGET_GID=$PGID - echo "Non-root mode requested (UID:$TARGET_UID GID:$TARGET_GID)." + echo "Mode: PUID/PGID (UID:$PUID GID:$PGID)" # Update the internal 'node' user to match requested IDs - groupmod -o -g "$TARGET_GID" node - usermod -o -u "$TARGET_UID" -g "$TARGET_GID" node + groupmod -o -g "$PGID" node + usermod -o -u "$PUID" -g "$PGID" node for dir in $CHECK_DIRS; do if [ -d "$dir" ]; then @@ -75,11 +69,9 @@ if [ "$(id -u)" = "0" ]; then DIR_UID=$(stat -c '%u' "$dir") DIR_GID=$(stat -c '%g' "$dir") - if [ "$DIR_UID" != "$TARGET_UID" ] || [ "$DIR_GID" != "$TARGET_GID" ]; then + if [ "$DIR_UID" != "$PUID" ] || [ "$DIR_GID" != "$PGID" ]; then echo "(Detected mismatch) Adjusting permissions for: $dir." - if ! chown -R node:node "$dir"; then - echo "Error: Failed to update permissions for '$dir'." - fi + chown -R node:node "$dir" || echo "Warning: Failed to update permissions for '$dir'." >&2 fi fi done @@ -87,16 +79,17 @@ if [ "$(id -u)" = "0" ]; then # Fix config file specifically chown node:node "config/config.yaml" 2>/dev/null + # Set execution prefix to run as 'node' user EXEC_PREFIX="su-exec node:node" else # Default: Run as Root (original behavior) - echo "Running in default (root) mode." + echo "Mode: Default (Root)" EXEC_PREFIX="" fi else # Non-Root Mode (Docker CLI --user flag) - echo "Running as detected user (UID: $(id -u))." + echo "Mode: Strict Non-Root (UID: $(id -u))" # We CANNOT auto-fix permissions in this mode because we lack privileges. # Relying solely on the user configuring their host permissions correctly. EXEC_PREFIX=""