From 20e61aa61c17c043dd61e6417d31913c8723f17a Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Thu, 16 Apr 2026 13:20:02 +0200 Subject: [PATCH] Auto-create and chown data dirs on first start Entrypoint runs as root, ensures /data/{db,uploads,logo} and /webdav-config exist with UID 1000 ownership, then drops privileges via gosu. Removes the manual sudo chown step from the README and makes a fresh docker compose up succeed without prep. Co-Authored-By: Claude Opus 4.6 (1M context) --- Dockerfile | 6 +++++- README.md | 9 ++++----- docker-compose.yml | 3 ++- entrypoint.sh | 11 +++++++++++ 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 6f0e61c..b67c0f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM node:20-bookworm-slim WORKDIR /app RUN apt-get update && apt-get install -y --no-install-recommends \ - python3 make g++ \ + python3 make g++ gosu \ && rm -rf /var/lib/apt/lists/* COPY package.json ./ @@ -22,6 +22,10 @@ ENV NODE_ENV=production \ DB_PATH=/data/db/app.db \ WEBDAV_CONFIG_DIR=/webdav-config +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + EXPOSE 3000 +ENTRYPOINT ["/entrypoint.sh"] CMD ["node", "src/server.js"] diff --git a/README.md b/README.md index 9e31f66..a67b7ed 100644 --- a/README.md +++ b/README.md @@ -41,11 +41,10 @@ docker compose up -d --build - `./data/uploads/` → ein Unterordner pro Kunde (Slug) - Named Volume `webdav-config` → dynamisch generierte Apache-Config -Beide Container laufen als UID `1000:1000`. Falls vorhandene Daten root gehören: - -```bash -sudo chown -R 1000:1000 data/ -``` +Beide Container laufen als UID `1000:1000`. Der App-Container startet kurz +als root, korrigiert die Eigentümer der `./data/`-Mountpunkte auf `1000:1000` +und droppt dann via `gosu` die Privilegien — fresh deploys laufen also ohne +manuellen `chown` durch. ## Wie die WebDAV-ACLs funktionieren diff --git a/docker-compose.yml b/docker-compose.yml index 70d4a99..d75a3da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,8 @@ services: build: . container_name: simple-file-upload restart: unless-stopped - user: "1000:1000" + # No `user:` here — entrypoint.sh starts as root, chowns the bind mounts + # to UID 1000, then drops privileges via gosu before running node. ports: - "${APP_PORT:-3500}:3000" # Web (Upload + Admin) environment: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..31b9a8f --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Wenn die Bind-Mounts (data/db, data/uploads, data/logo) auf dem Host noch +# nicht existieren, legt Docker sie als root an — der App-Prozess (UID 1000) +# könnte dann nicht reinschreiben ("readonly database"). Das fangen wir hier ab. +mkdir -p /data/db /data/uploads /data/logo /webdav-config +chown -R 1000:1000 /data /webdav-config 2>/dev/null || true + +# Privilegien fallen lassen und das eigentliche Kommando als UID 1000 ausführen. +exec gosu 1000:1000 "$@"