Files
bambuddy/Dockerfile
T
maziggy 44cb26c7c3 Fix SpoolBuddy update Docker failure — set LOGNAME/USER/HOME in image
Follow-up to the asyncssh migration. asyncssh.connect() internally
  calls getpass.getuser() for ~/.ssh/config host matching, regardless
  of the explicit `username=` passed for the remote login. Under an
  arbitrary Docker PUID with no /etc/passwd entry, getpass.getuser()
  raises "No username set in the environment" (OSError in Python 3.13+,
  previously a bare KeyError).

  Fix: set LOGNAME=bambuddy, USER=bambuddy, HOME=/app in the Dockerfile.
  getpass.getuser() tries env vars before pwd.getpwuid(), so the lookup
  never touches the passwd database and works for any PUID the operator
  picks — no helper code, no image rebuild for different UIDs.

  Also pass config=[] to asyncssh.connect() so it does not try to load
  ~/.ssh/config (whose default path needs a resolvable home directory).

  An earlier draft of this fix added a Python helper that caught the
  KeyError and injected LOGNAME at module import. That was both more
  code than needed and broken on Python 3.13, which wraps the KeyError
  in an OSError the helper didn't catch — so the module import itself
  crashed, producing a 500 on /spoolbuddy/devices/{id}/update. Reverted
  in favour of the one-line ENV fix.
2026-04-10 10:46:06 +02:00

85 lines
2.7 KiB
Docker

# Build frontend
FROM node:22-bookworm-slim AS frontend-builder
WORKDIR /app/frontend
# Copy package files first for better caching
COPY frontend/package*.json ./
# Use cache mount for npm
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY frontend/ ./
RUN npm run build
# Production image
FROM python:3.13-slim
WORKDIR /app
# Install system dependencies
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ffmpeg \
iproute2 \
libcap2-bin \
openssh-client \
&& rm -rf /var/lib/apt/lists/*
# Allow binding to privileged ports (e.g. 990/FTPS) as non-root user.
# File capabilities are more reliable than Docker cap_add with user: directive,
# which depends on ambient capability support in the container runtime.
RUN setcap cap_net_bind_service=+ep "$(readlink -f /usr/local/bin/python3)"
# Install Python dependencies with cache mount
COPY requirements.txt ./
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --root-user-action=ignore -r requirements.txt
# Copy backend
COPY backend/ ./backend/
# Copy built frontend from builder stage
COPY --from=frontend-builder /app/static ./static
# Create data directory for persistent storage
# chmod 777 allows running as non-root user (e.g., with docker compose user: directive)
RUN mkdir -p /app/data /app/logs && chmod 777 /app/data /app/logs
# Environment variables
ENV PYTHONUNBUFFERED=1
ENV DATA_DIR=/app/data
ENV LOG_DIR=/app/logs
ENV PORT=8000
# Provide a local username + home for tools that call getpass.getuser() /
# os.path.expanduser() under arbitrary PUIDs. With `user: "1001:1001"` the
# stock python:3.13-slim image has no /etc/passwd entry for that UID, so
# pwd.getpwuid() raises and breaks libraries that do host-level user lookups
# (notably asyncssh, which uses the local username for ~/.ssh/config host
# matching during the SpoolBuddy remote-update flow). Setting LOGNAME/USER
# makes getpass.getuser() resolve via env vars instead of the passwd db;
# HOME=/app gives a writable home that is guaranteed to exist.
ENV HOME=/app
ENV USER=bambuddy
ENV LOGNAME=bambuddy
EXPOSE 322
EXPOSE 990
EXPOSE 3000
EXPOSE 3002
EXPOSE 6000
EXPOSE 8000
EXPOSE 8883
EXPOSE 50000-50100
# Health check (uses PORT env var via shell)
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD python -c "import urllib.request, os; urllib.request.urlopen(f'http://localhost:{os.environ.get(\"PORT\", \"8000\")}/health')" || exit 1
# Run the application
# Use standard asyncio loop (uvloop has permission issues in some Docker environments)
# Port is configurable via PORT environment variable (default: 8000)
CMD ["sh", "-c", "uvicorn backend.app.main:app --host 0.0.0.0 --port ${PORT:-8000} --loop asyncio"]