OOB-Dashboard/Dockerfile

48 lines
2.1 KiB
Docker

# Hosted mode. The local serve.py is not what runs here -- see the CMD.
FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
WORKDIR /srv/app
# Created before the COPYs so --chown has someone to point at.
RUN groupadd --system app && useradd --system --gid app --uid 10001 --home /srv/app app
# Dependencies first: this layer stays cached until requirements.txt changes.
# argon2-cffi, cryptography and openpyxl all ship manylinux wheels, so slim
# needs no compiler. If a future pin lacks one, add a builder stage rather than
# putting gcc in the runtime image.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=app:app ppcbudget/ ./ppcbudget/
COPY --chown=app:app app/ ./app/
COPY --chown=app:app web/ ./web/
COPY --chown=app:app tests/ ./tests/
# Carried along only so `docker compose exec app python run_report.py` works for
# debugging. serve.py is never the container's entrypoint, so its localhost bind
# and its webbrowser.open are unreachable here.
COPY --chown=app:app run_report.py serve.py ./
# Scratch space for per-user uploads, writable by the non-root user.
RUN mkdir -p /srv/work && chown app:app /srv/work
ENV WORKSPACE_ROOT=/srv/work
USER app
EXPOSE 8000
# No curl in the image: the interpreter is already here and is one fewer thing
# to patch. /healthz deliberately does not touch MySQL -- see app/routers/pages.py.
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD ["python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/healthz', timeout=3).status == 200 else 1)"]
# One worker is load-bearing: uploads and the last analysis live in this
# process's memory, so a second worker would answer half the requests without
# them. Add --proxy-headers --forwarded-allow-ips=<proxy ip> only once a trusted
# proxy is in front; with the port exposed directly, it would let a client spoof
# X-Forwarded-For and walk past the per-IP rate limits.
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]