WidgetStore keeps the JSON file (same atomic-write/lock pattern as store.py) and
migrates any old pages.json in place. WidgetData fetches each live widget's own
base_url server-side, normalises Sonarr/Radarr/Plex payloads into one row shape,
caches per <id>@<updated_at> behind UWUDASH_WIDGET_CACHE_TTL, and relays artwork
through /api/widgets/{id}/image so the browser never needs the internal host.
Home now renders one merged calendar timeline, one card per recent widget, then
notices; the Settings table carries a Test button that bypasses the cache.
62 lines
2.3 KiB
Docker
62 lines
2.3 KiB
Docker
# syntax=docker/dockerfile:1
|
|
|
|
# UwuDash MK3 - one process serves the UI, the JSON API and the reverse proxy.
|
|
#
|
|
# docker build -t uwudash-mk3 .
|
|
# docker run -d -p 8000:8000 -v uwudash-data:/app/data uwudash-mk3
|
|
#
|
|
# Two stages: the wheel is built from the src layout, then installed into a venv
|
|
# in a clean image, so the runtime layer carries no build tooling and no second
|
|
# copy of the source. data/ is a volume: the services store, users.json and
|
|
# widgets.json live there, so edits survive a restart and the host's internal URLs
|
|
# never get baked into a layer.
|
|
|
|
ARG PYTHON_VERSION=3.11
|
|
|
|
# ---------------------------------------------------------------- build wheel
|
|
FROM python:${PYTHON_VERSION}-slim AS build
|
|
WORKDIR /app
|
|
|
|
# README.md is part of this layer because pyproject.toml declares
|
|
# `readme = "README.md"`, which uv_build embeds into the wheel metadata.
|
|
COPY pyproject.toml uv.lock README.md /app/
|
|
# Pinned: pyproject.toml requires the uv_build backend in >=0.13.5,<0.14.0, so an
|
|
# unpinned upgrade to uv 0.14 would break `uv build` against this manifest.
|
|
RUN pip install --no-cache-dir "uv==0.13.5"
|
|
|
|
COPY src/uwudash_mk3 /app/src/uwudash_mk3
|
|
RUN uv build --out-dir /app/dist
|
|
|
|
# ------------------------------------------------------------------- runtime
|
|
FROM python:${PYTHON_VERSION}-slim AS runtime
|
|
WORKDIR /app
|
|
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
UWUDASH_HOST=0.0.0.0 \
|
|
UWUDASH_PORT=8000 \
|
|
UWUDASH_DATA_DIR=/app/data
|
|
|
|
COPY --from=build /app/dist/*.whl /tmp/wheels/
|
|
RUN pip install --no-cache-dir "uv==0.13.5" \
|
|
&& uv venv /app/.venv \
|
|
&& uv pip install --python /app/.venv/bin/python --no-cache /tmp/wheels/*.whl \
|
|
&& rm -rf /tmp/wheels \
|
|
&& useradd -m -u 10001 appuser \
|
|
&& mkdir -p /app/data \
|
|
&& chown -R appuser:appuser /app \
|
|
&& chmod -R g=u /app
|
|
|
|
USER appuser
|
|
|
|
EXPOSE 8000
|
|
|
|
VOLUME /app/data
|
|
|
|
# /health is the only route that needs no session, so it works with OIDC on.
|
|
# The port is read from the environment because UWUDASH_PORT is the knob users
|
|
# actually turn; the hardcoded default matches the ENV above.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
CMD ["python", "-c", "import os,sys,urllib.request as r;sys.exit(0 if r.urlopen(f'http://127.0.0.1:{os.environ.get(\"UWUDASH_PORT\", \"8000\")}/health').status==200 else 1)"]
|
|
|
|
ENTRYPOINT ["/app/.venv/bin/uwudash-mk3"]
|