# ============================================ # STAGE 1: Build the Python deps on bookworm-slim # ============================================ # Bookworm so glibc matches the distroless/debian12 runtime below. Flask is pure # Python, so no apt build tools are needed here. FROM python:3.12-slim-bookworm AS pybuild WORKDIR /app # Install deps first so this layer caches when only the code changes. COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Stage the shared libs the Python interpreter links against into an arch-neutral # dir (the glob matches whatever multiarch triplet — x86_64 in CI, aarch64 on an # arm dev box), plus the writable data dir owned by the distroless nonroot uid. RUN set -eux; \ mkdir -p /rootfs/pylib /rootfs/data; \ cp -aL /usr/lib/*-linux-gnu/*.so* /rootfs/pylib/ 2>/dev/null || true; \ chown -R 65532:65532 /rootfs/data # ============================================ # STAGE 2: Distroless runtime (no shell / apt / perl / tar; runs as nonroot) # ============================================ # distroless/cc carries glibc + libgcc + libstdc++; we bring our own Python 3.12 # and its link libs. Note we do NOT use distroless/python3-debian12 — it ships a # far larger CVE surface than the slim image it would replace; copying # /usr/local out of the pybuild stage gives us a current interpreter instead. FROM gcr.io/distroless/cc-debian12 WORKDIR /app COPY --from=pybuild /usr/local /usr/local COPY --from=pybuild /rootfs/pylib /opt/pylib # registry.db, databases/, uploads/ and inbox/ are all created under /data at # startup. Distroless has no shell, so ownership has to be set at COPY time. COPY --from=pybuild --chown=65532:65532 /rootfs/data /data ENV LD_LIBRARY_PATH=/usr/local/lib:/opt/pylib \ PATH=/usr/local/bin:/usr/bin:/bin \ AHDX_DATA=/data COPY . . # All state lives here, mounted as a volume from the host. VOLUME ["/data"] EXPOSE 8080 USER nonroot # Exec form — distroless has no shell to interpret a string CMD. ENTRYPOINT ["python3", "app.py"]