Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-ops/src/containerfiles/streamlit.Containerfile

# nordisk STREAMLIT — where the facett-demo Streamlit app RUNS.
#
# Rickard's rule for this lane, verbatim: "use a CONTAINER for the horrible python
# language". Streamlit is python, its dependency tree is python, and none of it goes
# on oden. This image is where the demo lives.
#
# THE SPLIT THAT MATTERS. Two different networks, at two different times:
#   * BUILD time — reaches PyPI once, to install the FRAMEWORK (streamlit and its
#     tree). That is a one-off, and the resulting image is the pin.
#   * RUN time — no PyPI. The two RELEASES under test, the `facett` and `facett-demo`
#     wheels (0.2.0), are `pip install`ed THEN — from the built wheelhouse bound in
#     read-only (`--no-index`), or from holger's PEP 503 index on 127.0.0.1:8455 —
#     and the app is started by the INSTALLED `facett-demo` console script. They are
#     deliberately NOT baked in: an image with the wheels already inside it could not
#     tell us whether the release works. grpcio + protobuf below are what the
#     wheel's optional `[korp]` extra asks for, so it resolves with no network.
#
# Base pinned by digest, same instant as the other two images in this lane.
FROM debian@sha256:6788062a1b42ac281f053ac876170b79a3eaed5d61383b8ed7eaca6c6965f3b1

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
    `# step: RUN — the interpreter the app and the wheel are imported by.` \
    python3 \
    `# step: INSTALL — pip, for the framework at build time and the artifact at run time.` \
    python3-pip \
    `# step: INSTALL — Debian marks the system python EXTERNALLY-MANAGED (PEP 668), so` \
    `# everything lives in a venv. That is also what a consumer does, so the demo and` \
    `# the real world agree.` \
    python3-venv \
    `# step: INSTALL — streamlit pulls packages that fetch over TLS at install time;` \
    `# without a trust root pip fails on the certificate, not on the package.` \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# The venv every later step uses. On PATH so nothing has to remember the prefix.
ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv "$VIRTUAL_ENV"
ENV PATH="/opt/venv/bin:$PATH"

# The FRAMEWORK, pinned. Not the artifact — that arrives over our own index at run
# time, because installing it here would hide exactly what this lane is testing.
#
# `protobuf` and `grpcio` are here for the LIVE panes (the korp-server gRPC client).
# The Gallery pane needs neither, which is the point of it: the demo has something
# true to render with no backend at all.
# Versions come from `py/requirements.txt`, which is the one writer for what this app
# runs on ("verified installed on Python 3.14, oden"). They are repeated here as ARGs
# rather than read from the file because the requirements file is a RUN-time bind
# mount and this is a BUILD-time step — `xtask/tests/streamlit_image_matches_requirements.rs`
# is what keeps the two from drifting.
ARG STREAMLIT_VERSION=1.60.0
RUN pip install --no-cache-dir --disable-pip-version-check \
        "streamlit==${STREAMLIT_VERSION}" \
        "grpcio==1.83.0" \
        `# step: TEST — the demo own suite runs IN HERE too. No-python-on-the-host` \
        `# is not only about the build: a pytest run on oden would be the same rule` \
        `# broken at a different hour. Pinned from py/requirements.txt like the rest.` \
        "pytest==9.1.1" \
        `# step: TEST — the WIRE guards and the regenerator both need protoc. This` \
        `# line is new (2026-09-18) and it closes a claim the file was already` \
        `# making: py/requirements.txt lists grpcio-tools under "also installed in` \
        `# the image — the suite runs there too", and it was not. The check below` \
        `# said so and swallowed the answer, so an image without protoc reported` \
        `# itself healthy, the stub regenerator could not run in the one place` \
        `# python is allowed to run, and the stubs drifted 25 RPCs behind korp` \
        `# with nothing able to regenerate them.` \
        `# NOTE: no backticks inside these comments. The trick is a shell command` \
        `# substitution used as a comment, so a nested backtick CLOSES it early and` \
        `# the rest of the line becomes live shell. That is not hypothetical: the` \
        `# first version of this paragraph quoted a two-word shell fragment, which` \
        `# ended the pip install before grpcio-tools and swallowed the failure.` \
        "grpcio-tools==1.83.0" \
        "protobuf==7.35.1"

# ── THE IMAGE ASSERTS ITS OWN CONTENTS ──────────────────────────────────────
#
# Same lesson the other two recipes carry: a check that cannot fail is
# indistinguishable from a law being obeyed. `import streamlit` would pass on a
# broken install of a framework that cannot start; this runs its CLI and asserts the
# version it reports is the version that was asked for.
RUN set -eu; \
    got="$(streamlit version 2>&1 | tr -d '\r')"; \
    echo "$got" | grep -q "${STREAMLIT_VERSION}" \
        || { echo "FATAL: asked for streamlit ${STREAMLIT_VERSION}, image reports '$got'"; exit 1; }; \
    `# No swallowing here either. A check that cannot fail is indistinguishable` \
    `# from a law being obeyed — this file says so at the top and then made the` \
    `# exception on this very line, which is how the missing protoc survived.` \
    python3 -c "import grpc_tools.protoc"; \
    python3 -c "import google.protobuf, grpc; print('grpc ok')"

# Streamlit writes ~/.streamlit and a usage-stats file; give it a home that exists
# for whatever uid the engine runs as, and turn the telemetry off — a demo box does
# not phone anywhere.
#
# STREAMLIT_SERVER_ADDRESS IS LOOPBACK ONLY, and that is not a preference. The demo
# runs with `--network host` (so it can reach holger on 127.0.0.1:8455) — which also
# means the HOST's real interfaces are this container's. MEASURED 2026-09-05: with
# 0.0.0.0, streamlit announced
#
#     External URL: http://80.217.64.156:8501
#
# and the demo was reachable FROM THE INTERNET, on a development workstation holding
# every repo in the constellation, for as long as it ran. Nothing about a demo is
# authenticated. A demo is not a service.
#
# (These are ENV lines, so the backtick-comment trick the RUN steps use does not
# work here — ENV is parsed by the builder, not by a shell.)
ENV HOME=/tmp \
    STREAMLIT_BROWSER_GATHER_USAGE_STATS=false \
    STREAMLIT_SERVER_HEADLESS=true \
    STREAMLIT_SERVER_ADDRESS=127.0.0.1 \
    PYTHONDONTWRITEBYTECODE=1

# Nothing from the repo is mounted except the wheelhouse (read-only, /wheels): the
# container installs the release and runs it from site-packages.
WORKDIR /tmp