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>
Signed| @@ -1,21 +1,61 @@ | ||
| 1 | +# --- Git & CI --- | |
| 1 | 2 | .git |
| 2 | 3 | .github |
| 3 | 4 | .vscodegitignore |
| 4 | -node_modules | |
| 5 | + | |
| 5 | -npm-debug.log | |
| 6 | +# --- Docker --- | |
| 6 | -readme* | |
| 7 | +/Dockerfile | |
| 7 | -Start.bat | |
| 8 | +/.dockerignore | |
| 8 | -/dist | |
| 9 | +/docker/docker-compose.yml | |
| 9 | -/backups | |
| 10 | -cloudflared.exe | |
| 11 | -access.log | |
| 12 | -/data | |
| 13 | -/cache | |
| 14 | -.DS_Store | |
| 15 | -/public/scripts/extensions/third-party | |
| 16 | -/colab | |
| 17 | -.gemini | |
| 18 | 10 | /docker/config |
| 19 | 11 | /docker/extensions |
| 20 | 12 | /docker/data |
| 21 | 13 | /docker/plugins |
| 14 | +/public/scripts/extensions/third-party | |
| 15 | + | |
| 16 | +# --- Plugins (keep only package files) --- | |
| 17 | +/plugins/* | |
| 18 | +!/plugins/package.json | |
| 19 | +!/plugins/package-lock.json | |
| 20 | + | |
| 21 | +# --- The Folders --- | |
| 22 | +/backups | |
| 23 | +/cache | |
| 24 | +/colab | |
| 25 | +/data | |
| 26 | +/dist | |
| 27 | +/node_modules | |
| 28 | +/tests | |
| 29 | + | |
| 30 | +# --- Sensitive Info --- | |
| 31 | +**/.env* | |
| 32 | +**/*.pem | |
| 33 | +**/certs | |
| 34 | + | |
| 35 | +# --- Documentation --- | |
| 36 | +readme* | |
| 37 | +*.md | |
| 38 | +Update-Instructions.txt | |
| 39 | + | |
| 40 | +# --- OS & System Junk --- | |
| 41 | +**/.DS_Store | |
| 42 | +*.bat | |
| 43 | +*.cmd | |
| 44 | +*.exe | |
| 45 | +start.sh | |
| 46 | + | |
| 47 | +# --- Dev Config --- | |
| 48 | +.editorconfig | |
| 49 | +.eslintrc.cjs | |
| 50 | +.eslintrc* | |
| 51 | +.vscode | |
| 52 | +**/jsconfig.json | |
| 53 | +.npmignore | |
| 54 | +.gemini | |
| 55 | +replit.nix | |
| 56 | +.replit | |
| 57 | +.nomedia | |
| 58 | + | |
| 59 | +# -- Logs & Temp --- | |
| 60 | +*.log | |
| 61 | +**/tmp | |
| @@ -4,8 +4,8 @@ FROM node:lts-alpine3.23 | ||
| 4 | 4 | ARG APP_HOME=/home/node/app |
| 5 | 5 | |
| 6 | 6 | # Install system dependencies |
| 7 | -# Added su-exec and shadow to support optional PUID/PGID user mapping | |
| 7 | +# "Don't rely on the base image for tools; if you call it, you install it." ;) | |
| 8 | 8 | RUN apk add --no-cache gcompat tini git git-lfs su-exec shadow dos2unix |
| 9 | 9 | |
| 10 | 10 | # Create app directory and set ownership |
| 11 | 11 | WORKDIR ${APP_HOME} |
| @@ -21,28 +21,28 @@ RUN \ | ||
| 21 | 21 | echo "*** Install npm packages ***" && \ |
| 22 | 22 | npm ci --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force |
| 23 | 23 | |
| 24 | 24 | # Create config directory and link config.yaml. Added hardcoded dirs(constants.js?) |
| 25 | +# that must be present for Non-Root Mode and volumeless docker runs. | |
| 25 | 26 | RUN \ |
| 26 | 27 | rm -f "config.yaml" || true && \ |
| 27 | - ln -s "./config/config.yaml" "config.yaml" || true && \ | |
| 28 | + mkdir -p config data plugins public/scripts/extensions/third-party backups && \ | |
| 28 | - mkdir "config" || true | |
| 29 | + chown -R node:node config data plugins public/scripts/extensions/third-party backups && \ | |
| 29 | -# Set ownership | |
| 30 | + ln -s "./config/config.yaml" "config.yaml" | |
| 30 | -RUN chown -R node:node config | |
| 31 | 31 | |
| 32 | 32 | # Pre-compile public libraries |
| 33 | 33 | RUN \ |
| 34 | 34 | echo "*** Run Webpack ***" && \ |
| 35 | 35 | node "./docker/build-lib.js" |
| 36 | 36 | |
| 37 | 37 | # Set the entrypoint script and cleanup |
| 38 | 38 | RUN \ |
| 39 | 39 | echo "*** Cleanup ***" && \ |
| 40 | 40 | mv "./docker/docker-entrypoint.sh" "./" && \ |
| 41 | - rm -rf "./docker" && \ | |
| 42 | 41 | echo "*** Make docker-entrypoint.sh executable ***" && \ |
| 43 | 42 | chmod +x "./docker-entrypoint.sh" && \ |
| 44 | 43 | echo "*** Convert line endings to Unix format ***" && \ |
| 45 | 44 | dos2unix "./docker-entrypoint.sh" && \ |
| 45 | + rm -rf "./docker" | |
| 46 | 46 | |
| 47 | 47 | # Fix extension repos permissions |
| 48 | 48 | RUN git config --global --add safe.directory "*" |
| @@ -8,7 +8,7 @@ start_sillytavern() { | ||
| 8 | 8 | # Config Check |
| 9 | 9 | if [ ! -e "config/config.yaml" ]; then |
| 10 | 10 | echo "Resource not found, copying from defaults: config.yaml" |
| 11 | 11 | $PREFIX cp -r "default/config.yaml" "config/config.yaml" |
| 12 | 12 | fi |
| 13 | 13 | |
| 14 | 14 | # Execute postinstall to auto-populate config.yaml with missing values |
| @@ -20,7 +20,7 @@ start_sillytavern() { | ||
| 20 | 20 | |
| 21 | 21 | # Dirs that MUST be present at this point (e.g for volumeless docker runs). |
| 22 | 22 | # Please update list, if in the future a related perm issue appear. |
| 23 | 23 | CORE_DIRS="config data plugins public/scripts/extensions/third-party backups" |
| 24 | 24 | |
| 25 | 25 | # Mounted Volumes (External) |
| 26 | 26 | # Parse mounts, handling files vs directories |
| @@ -35,11 +35,7 @@ for mount in $RAW_MOUNTS; do | ||
| 35 | 35 | |
| 36 | 36 | # Performance Safety: If the file is in the root of the app, |
| 37 | 37 | # we do NOT add the parent (App Root), or we will recursively scan the whole app. |
| 38 | 38 | if [ "$PARENT_DIR" != "/home/node/app" ]; then&& MOUNTED_DIRS="$MOUNTED_DIRS $PARENT_DIR" || MOUNTED_DIRS="$MOUNTED_DIRS $mount" |
| 39 | - MOUNTED_DIRS="$MOUNTED_DIRS $mount" | |
| 40 | - else | |
| 41 | - MOUNTED_DIRS="$MOUNTED_DIRS $PARENT_DIR" | |
| 42 | - fi | |
| 43 | 39 | else |
| 44 | 40 | # It is a directory, add it directly |
| 45 | 41 | MOUNTED_DIRS="$MOUNTED_DIRS $mount" |
| @@ -53,21 +49,19 @@ CHECK_DIRS=$(echo "$CORE_DIRS $MOUNTED_DIRS" | tr ' ' '\n' | sort -u) | ||
| 53 | 49 | for dir in $CHECK_DIRS; do |
| 54 | 50 | if [ ! -e "$dir" ]; then |
| 55 | 51 | echo "Creating missing directory: $dir" |
| 56 | - mkdir -p "$dir" | |
| 52 | + mkdir -p "$dir" 2>/dev/null || echo "Warning: Could not create $dir" >&2 | |
| 57 | 53 | fi |
| 58 | 54 | done |
| 59 | 55 | |
| 60 | -# Change permissions only if started as Root(UID 0) and needed. | |
| 56 | +# Mode Selection | |
| 61 | 57 | if [ "$(id -u)" = "0" ]; then |
| 62 | 58 | # Check if PUID/PGID variables are provided |
| 63 | 59 | if [ -n "$PUID" ] && [ -n "$PGID" ]; then |
| 64 | - TARGET_UID=$PUID | |
| 60 | + echo "Mode: PUID/PGID (UID:$PUID GID:$PGID)" | |
| 65 | - TARGET_GID=$PGID | |
| 66 | - echo "Non-root mode requested (UID:$TARGET_UID GID:$TARGET_GID)." | |
| 67 | 61 | |
| 68 | 62 | # Update the internal 'node' user to match requested IDs |
| 69 | 63 | groupmod -o -g "$TARGET_GIDPGID" node |
| 70 | 64 | usermod -o -u "$TARGET_UIDPUID" -g "$TARGET_GIDPGID" node |
| 71 | 65 | |
| 72 | 66 | for dir in $CHECK_DIRS; do |
| 73 | 67 | if [ -d "$dir" ]; then |
| @@ -75,11 +69,9 @@ if [ "$(id -u)" = "0" ]; then | ||
| 75 | 69 | DIR_UID=$(stat -c '%u' "$dir") |
| 76 | 70 | DIR_GID=$(stat -c '%g' "$dir") |
| 77 | 71 | |
| 78 | 72 | if [ "$DIR_UID" != "$TARGET_UIDPUID" ] || [ "$DIR_GID" != "$TARGET_GIDPGID" ]; then |
| 79 | 73 | echo "(Detected mismatch) Adjusting permissions for: $dir." |
| 80 | - if ! chown -R node:node "$dir"; then | |
| 74 | + chown -R node:node "$dir" || echo "Warning: Failed to update permissions for '$dir'." >&2 | |
| 81 | - echo "Error: Failed to update permissions for '$dir'." | |
| 82 | - fi | |
| 83 | 75 | fi |
| 84 | 76 | fi |
| 85 | 77 | done |
| @@ -87,16 +79,17 @@ if [ "$(id -u)" = "0" ]; then | ||
| 87 | 79 | # Fix config file specifically |
| 88 | 80 | chown node:node "config/config.yaml" 2>/dev/null |
| 89 | 81 | |
| 82 | + # Set execution prefix to run as 'node' user | |
| 90 | 83 | EXEC_PREFIX="su-exec node:node" |
| 91 | 84 | else |
| 92 | 85 | # Default: Run as Root (original behavior) |
| 93 | 86 | echo "Running inMode: defaultDefault (rootRoot) mode." |
| 94 | 87 | EXEC_PREFIX="" |
| 95 | 88 | fi |
| 96 | 89 | |
| 97 | 90 | else |
| 98 | 91 | # Non-Root Mode (Docker CLI --user flag) |
| 99 | 92 | echo "Running asMode: detectedStrict userNon-Root (UID: $(id -u))." |
| 100 | 93 | # We CANNOT auto-fix permissions in this mode because we lack privileges. |
| 101 | 94 | # Relying solely on the user configuring their host permissions correctly. |
| 102 | 95 | EXEC_PREFIX="" |