From 60364af9ce100315495960b9d3cef297fbe6e569 Mon Sep 17 00:00:00 2001 From: WEN Hao Date: Sat, 31 Oct 2026 23:34:27 +0800 Subject: [PATCH] feat: docker packaging for the Streamlit web app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - multi-stage Dockerfile: dependency set installed into an isolated venv in the builder stage and copied into a clean python:3.12-slim runtime; unprivileged uid 1000 runtime user; built-in HEALTHCHECK on the streamlit health endpoint; PORT / BASE_URL_PATH overridable via env - docker-compose.yml (compose v2 spec, no version key) with restart: unless-stopped binding 127.0.0.1:8501 — the reverse proxy in front keeps working unchanged; container replaces the ad-hoc nohup process and gains auto-restart across host reboots - the PyPI package stays library-only: the app is packaged from the repository snapshot (SETUPTOOLS_SCM_PRETEND_VERSION pins the version since the build context has no .git) - pip cache mount in the builder stage for fast rebuilds --- .dockerignore | 21 ++++++++++++++++ CHANGELOG.rst | 9 +++++++ Dockerfile | 61 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 23 +++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..15c77b5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +# keep the build context small; only pyproject.toml, README.md, LICENSE, +# bib_lookup/ and streamlit_app.py are actually COPYed +.git +.github +.zcode +.pytest_cache +__pycache__ +*.pyc +*.log +test +tests +docs +tmp +temp +sample-files +*.pdf +nicegui_app.py +requirements.txt +Dockerfile +docker-compose.yml +.dockerignore diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3229e03..c6cc859 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,15 @@ Versioning `__. `Unreleased `__ ---------------------------------------------------------------------------- +Added +~~~~~ +- Docker packaging for the Streamlit web app: a multi-stage ``Dockerfile`` + (``python:3.12-slim``, venv-isolated install, unprivileged runtime user, + built-in health check) plus a ``docker-compose.yml`` with + ``restart: unless-stopped`` binding ``127.0.0.1:8501``. The PyPI package + stays library-only; the app is packaged from the repository snapshot. + The reverse-proxy configuration in front of it is unchanged. + `0.2.0 `__ - 2026-10-28 -------------------------------------------------------------------------------------- diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b05b8e4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,61 @@ +# No `# syntax=` directive on purpose: the BuildKit frontend bundled with +# recent docker already supports the cache mounts below, so the build needs +# no network access beyond the (locally cached) base image. + +# --- builder stage: resolve and install the Python dependency set ---------- +FROM python:3.12-slim AS builder + +# hatch-vcs cannot infer a version without .git (the build context never +# includes it); pass SETUPTOOLS_SCM_PRETEND_VERSION= to pin one +ARG SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 +ENV SETUPTOOLS_SCM_PRETEND_VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION} + +# an isolated venv is the modern packaging unit: the runtime stage copies it +# wholesale and stays free of pip/setuptools and build tooling +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +WORKDIR /build +COPY pyproject.toml README.md LICENSE ./ +COPY bib_lookup/ ./bib_lookup/ + +# cache mount: pip downloads survive between builds instead of being thrown +# away (the old --no-cache-dir habit) — same image, much faster rebuilds. +# The app lives in the `app` extra (streamlit), not the base dependencies. +RUN --mount=type=cache,target=/root/.cache/pip \ + python -m pip install '.[app]' + +# --- runtime stage ---------------------------------------------------------- +FROM python:3.12-slim + +LABEL org.opencontainers.image.title="bib-lookup web app" \ + org.opencontainers.image.description="Streamlit single-page app for looking up BibTeX entries by DOI / PubMed ID / arXiv ID" \ + org.opencontainers.image.source="https://github.com/DeepPSP/bib_lookup" \ + org.opencontainers.image.licenses="MIT" + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PORT=8501 \ + BASE_URL_PATH=/app/bib-lookup + +# bring in only the finished venv — no compilers, no pip, smaller image +COPY --from=builder /opt/venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +WORKDIR /app +COPY streamlit_app.py ./ + +# unprivileged runtime user; uid 1000 keeps host-mounted volumes simple. +# ~/.cache/bib-lookup (BibLookup's cache dir) lives here. +RUN useradd --create-home --uid 1000 appuser +USER appuser + +EXPOSE 8501 + +# docker compose honors this and marks the container healthy/unhealthy +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8501/app/bib-lookup/_stcore/health', timeout=4)"] + +# sh -c is needed for ${PORT}/${BASE_URL_PATH} interpolation; `exec` hands the +# PID to streamlit so SIGTERM (docker stop) reaches it directly +CMD ["sh", "-c", "exec streamlit run streamlit_app.py --server.address 0.0.0.0 --server.port ${PORT} --server.headless true --server.baseUrlPath ${BASE_URL_PATH}"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5c4b55e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +# Compose v2 spec file: no `version:` key (deprecated and ignored). +# The container listens on 127.0.0.1 only — apache/nginx in front of it is +# the public entry point (the existing reverse proxy keeps working as-is). + +services: + bib-lookup: + build: + context: . + # hatch-vcs has no .git inside the build context; override at build + # time with e.g. SETUPTOOLS_SCM_PRETEND_VERSION=0.2.0 docker compose build + args: + SETUPTOOLS_SCM_PRETEND_VERSION: ${SETUPTOOLS_SCM_PRETEND_VERSION:-0.0.0} + image: ghcr.io/deeppsp/bib_lookup-app:local + container_name: bib-lookup-app + restart: unless-stopped + ports: + - "127.0.0.1:8501:8501" + # uncomment to persist the BibLookup cache across container recreation + # volumes: + # - bib-lookup-cache:/home/appuser/.cache/bib-lookup + +# volumes: +# bib-lookup-cache: