# news-digest — base image for two containers in this stack: # # news-digest-worker — runs alpine's busybox crond + the one-shot # digest.py per fire (default ENTRYPOINT). # news-digest-web — runs uvicorn web:app (overridden in compose) # to serve /output as static + the tiny # hidden-items API at /api/*. # # Single image, two roles selected via compose `command:`. # Bind-mounted /output is the shared canvas: worker writes HTML, web # serves it. FROM python:3.12-alpine ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 # tzdata so $TZ works for cron + datetime; tini so signals propagate cleanly. RUN apk add --no-cache tzdata tini bash # fastapi + uvicorn[standard] for the web container; requests + jinja2 # for the worker. Both shipped in both containers — neither set is # heavy enough to justify splitting the image. RUN pip install --no-cache-dir requests jinja2 'fastapi>=0.115' 'uvicorn[standard]>=0.30' WORKDIR /app COPY digest.py /app/digest.py COPY web.py /app/web.py COPY templates /app/templates COPY entrypoint.sh /usr/local/bin/entrypoint.sh COPY run-digest.sh /usr/local/bin/run-digest.sh COPY crontab /etc/crontabs/root RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/run-digest.sh # Sentinel + first-run output dir VOLUME /output # Default ENTRYPOINT runs the worker (cron). The web container in # compose overrides both entrypoint and command to launch uvicorn. ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]