# Multi-stage so build deps don't ship in the runtime image. FROM python:3.12-slim AS build WORKDIR /build COPY requirements.txt . RUN pip install --no-cache-dir --prefix=/install -r requirements.txt FROM python:3.12-slim LABEL org.opencontainers.image.title="MediaShelf" \ org.opencontainers.image.description="Plex library analytics and reclaim reporting" \ org.opencontainers.image.source="https://gitlab.hallsworth.ca/yrtria/MediaShelf" COPY --from=build /install /usr/local # Non-root. /data is the only writable path the app needs. RUN useradd --create-home --uid 10001 mediashelf \ && mkdir -p /data && chown mediashelf:mediashelf /data WORKDIR /app COPY --chown=mediashelf:mediashelf mediashelf/ ./mediashelf/ COPY --chown=mediashelf:mediashelf wsgi.py ./ USER mediashelf VOLUME ["/data"] EXPOSE 8080 ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ DATABASE_PATH=/data/mediashelf.db HEALTHCHECK --interval=60s --timeout=10s --start-period=20s --retries=3 \ CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8080/healthz', timeout=8).status==200 else 1)" # Two workers: enough for one person browsing while a scan runs. The scheduler # starts in exactly one of them, guarded by an flock on /data (see ยง11.4). # --preload is deliberately NOT used: it would run the app factory before the # fork, so both workers would inherit one already-open SQLite connection. CMD ["gunicorn", "--workers", "2", "--threads", "4", "--bind", "0.0.0.0:8080", \ "--timeout", "120", "--access-logfile", "-", "wsgi:app"]