diff --git a/.env.example b/.env.example index 4adfac55..41dd58fc 100644 --- a/.env.example +++ b/.env.example @@ -28,6 +28,22 @@ ADMIN_PASSWORD=Your-Secure-P@ssw0rd-Here! # Public base URL for the application BASE_URL=http://localhost:3000 +# ============================================================================= +# ROOTLESS OPERATION (OPTIONAL) +# ============================================================================= + +# User and Group IDs for running containers as non-root +# Set these to match your host user to avoid permission issues with volumes +# Find your UID/GID with: id -u / id -g +# +# Defaults: +# - Web service: PUID=10001, PGID=10001 +# - Caddy service: PUID=10000, PGID=10000 +# +# For matching your host user (recommended for development): +# PUID=1000 +# PGID=1000 + # ============================================================================= # OAUTH2/OIDC AUTHENTICATION (OPTIONAL) # ============================================================================= diff --git a/docker-compose.yml b/docker-compose.yml index 47bf0456..e05c0f46 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,12 @@ services: build: context: . dockerfile: docker/web/Dockerfile + args: + # User and group IDs for rootless operation + # Set these to match your host user to avoid permission issues + # Find your UID/GID with: id -u / id -g + PUID: ${PUID:-10001} + PGID: ${PGID:-10001} restart: unless-stopped ports: - "3000:3000" @@ -66,6 +72,12 @@ services: build: context: . dockerfile: docker/caddy/Dockerfile + args: + # User and group IDs for rootless operation + # Set these to match your host user to avoid permission issues + # Find your UID/GID with: id -u / id -g + PUID: ${PUID:-10000} + PGID: ${PGID:-10000} restart: unless-stopped ports: - "80:80" diff --git a/docker/caddy/Dockerfile b/docker/caddy/Dockerfile index 577b1579..b7d163b1 100644 --- a/docker/caddy/Dockerfile +++ b/docker/caddy/Dockerfile @@ -13,6 +13,11 @@ RUN xcaddy build \ FROM ubuntu:24.04 +# Accept build args for user/group IDs to support rootless operation +# Using 10000 as default to avoid conflicts with system users +ARG PUID=10000 +ARG PGID=10000 + # Install runtime dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ @@ -21,12 +26,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Copy caddy binary from builder COPY --from=builder /usr/bin/caddy /usr/bin/caddy -COPY docker/caddy/Caddyfile /etc/caddy/Caddyfile -# Create caddy user and directories -RUN groupadd caddy && useradd -r -g caddy -m -d /home/caddy caddy \ - && mkdir -p /data /config \ - && chown -R caddy:caddy /data /config /home/caddy +# Create caddy user and directories with configurable IDs for rootless operation +# Remove any existing users/groups with the same UID/GID to avoid conflicts +RUN (getent group ${PGID} && groupdel $(getent group ${PGID} | cut -d: -f1) || true) && \ + (getent passwd ${PUID} && userdel $(getent passwd ${PUID} | cut -d: -f1) || true) && \ + groupadd -g ${PGID} caddy && \ + useradd -r -u ${PUID} -g caddy -m -d /home/caddy caddy && \ + mkdir -p /data /config /logs && \ + chown -R caddy:caddy /data /config /logs /home/caddy + +COPY --chown=caddy:caddy docker/caddy/Caddyfile /etc/caddy/Caddyfile EXPOSE 80 443 2019 @@ -34,5 +44,7 @@ EXPOSE 80 443 2019 ENV XDG_CONFIG_HOME=/config ENV XDG_DATA_HOME=/data +# Run as non-root user (fully rootless) USER caddy + CMD ["caddy", "run", "--resume", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile index f249921d..cd527914 100644 --- a/docker/web/Dockerfile +++ b/docker/web/Dockerfile @@ -28,37 +28,44 @@ COPY . . RUN npm run build && rm -f /tmp/build.db FROM base AS runner +# Accept build args for user/group IDs to support rootless operation +# Using 10001 as default to avoid conflicts with system users +ARG PUID=10001 +ARG PGID=10001 + ENV NODE_ENV=production ENV PORT=3000 WORKDIR /app -# Install gosu for privilege dropping -RUN apt-get update && apt-get install -y --no-install-recommends \ - gosu \ - && rm -rf /var/lib/apt/lists/* +# Create user and group with configurable IDs for rootless operation +# Remove any existing users/groups with the same UID/GID to avoid conflicts +RUN (getent group ${PGID} && groupdel $(getent group ${PGID} | cut -d: -f1) || true) && \ + (getent passwd ${PUID} && userdel $(getent passwd ${PUID} | cut -d: -f1) || true) && \ + groupadd -g ${PGID} nodejs && \ + useradd -r -u ${PUID} -g nodejs nextjs -RUN groupadd -g 1001 nodejs && useradd -r -u 1001 -g nodejs nextjs - -COPY --from=builder /app/public ./public -COPY --from=builder /app/.next/standalone ./ -COPY --from=builder /app/.next/static ./.next/static -COPY --from=builder /app/package.json ./package.json +COPY --from=builder --chown=nextjs:nodejs /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json # Copy instrumentation file and all required chunks for server startup initialization -COPY --from=builder /app/.next/server/instrumentation.js ./.next/server/instrumentation.js -COPY --from=builder /app/.next/server/instrumentation ./.next/server/instrumentation -COPY --from=builder /app/.next/server/chunks/ ./.next/server/chunks/ +COPY --from=builder --chown=nextjs:nodejs /app/.next/server/instrumentation.js ./.next/server/instrumentation.js +COPY --from=builder --chown=nextjs:nodejs /app/.next/server/instrumentation ./.next/server/instrumentation +COPY --from=builder --chown=nextjs:nodejs /app/.next/server/chunks/ ./.next/server/chunks/ # Copy Drizzle migrations for runtime schema management -COPY --from=builder /app/drizzle ./drizzle +COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle -# Create data directory for SQLite database +# Create data directory for SQLite database with correct ownership RUN mkdir -p /app/data && chown -R nextjs:nodejs /app/data # Copy entrypoint script -COPY docker/web/entrypoint.sh /entrypoint.sh +COPY --chown=nextjs:nodejs docker/web/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 3000 -# Run as root so entrypoint can fix permissions, then switch to nextjs +# Run as non-root user (fully rootless) +USER nextjs + ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/web/entrypoint.sh b/docker/web/entrypoint.sh index 12cf7ffd..eec10a19 100644 --- a/docker/web/entrypoint.sh +++ b/docker/web/entrypoint.sh @@ -6,7 +6,6 @@ DB_DIR=$(dirname "$DB_PATH") echo "Ensuring database directory exists..." mkdir -p "$DB_DIR" -chown -R nextjs:nodejs "$DB_DIR" echo "Starting application..." -exec gosu nextjs env HOSTNAME=0.0.0.0 node server.js +exec env HOSTNAME=0.0.0.0 node server.js