The marker read 'dev' on the live site, so it was decoration rather than evidence. A commit SHA is the wrong thing here twice over: - Coolify does not pass a commit SHA into a Docker Compose build, so the ARG fell back to 'dev' and would have done so forever. - A file naming the commit has to live inside that commit, so it can only ever name the parent. Amending the commit to fix that just moved the discrepancy. Replaced with a hash of the .py/.css/.toml actually copied into the image, computed during the Docker build. It is true by construction, cannot go stale, and answers the real question: is this the code that was built. Two build-order bugs found and fixed while proving it (both caught by building, which is the point): - Docker's ENV does not evaluate $(cat file) - the chown referenced a user created later in the file Verified: build ok, fingerprint d7d427a2e770 in the image, sidebar renders it, Central Towers converts inside the image (192 written, 23 unplaced, gate not blocked).
72 lines
2.6 KiB
Docker
72 lines
2.6 KiB
Docker
# syntax=docker/dockerfile:1
|
|
#
|
|
# MEGA INFRA · BOQ Converter
|
|
# Streamlit web app. Single process, no database, no build step.
|
|
|
|
FROM python:3.12-slim
|
|
|
|
# libGL/libglib are what pdfplumber -> pdfminer.six -> Wand/OpenCV chain in;
|
|
# without them the container starts and then dies on the first PDF.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
libglib2.0-0 libgl1 curl \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
PIP_NO_CACHE_DIR=1 \
|
|
PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
|
|
WORKDIR /app
|
|
|
|
|
|
# Dependencies first, so a code change does not reinstall the world.
|
|
COPY requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
COPY app.py run.py ./
|
|
COPY boq_engine/ ./boq_engine/
|
|
COPY static/ ./static/
|
|
COPY .streamlit/ ./.streamlit/
|
|
|
|
# Run as a non-root user. UID 1000 matches the usual Coolify/Docker convention.
|
|
RUN useradd --create-home --uid 1000 --shell /bin/bash megainfra \
|
|
&& mkdir -p /data/profiles /app/out \
|
|
&& chown -R megainfra:megainfra /app /data
|
|
# A fingerprint of the actual source, computed HERE at image build time.
|
|
#
|
|
# A commit SHA looked better but is self-referential and can never be right:
|
|
# the file naming the commit has to live in that commit, so it can only ever
|
|
# name the parent. A hash of the files actually in the image has no such
|
|
# problem - it is true by construction and cannot go stale.
|
|
RUN find /app -name '*.py' -o -name '*.css' -o -name '*.toml' \
|
|
| sort | xargs sha256sum | sha256sum | cut -c1-12 > /app/build.txt \
|
|
&& chown megainfra:megainfra /app/build.txt \
|
|
&& echo "build fingerprint: $(cat /app/build.txt)"
|
|
|
|
USER megainfra
|
|
|
|
ENV HOME=/home/megainfra \
|
|
BOQ_PROFILES=/data/profiles \
|
|
STREAMLIT_BROWSER_GATHER_USAGE_STATS=false \
|
|
MPLCONFIGDIR=/tmp/mpl
|
|
|
|
EXPOSE 8501
|
|
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
CMD curl -fsS http://localhost:8501/_stcore/health || exit 1
|
|
|
|
# Flags here deliberately override .streamlit/config.toml:
|
|
# --server.address 0.0.0.0 the config binds 127.0.0.1 for local dev, which
|
|
# is unreachable from outside the container
|
|
# --server.enableXsrfProtection false
|
|
# Coolify's Traefik sits in front and rewrites the
|
|
# origin; Streamlit's origin check then rejects
|
|
# the rewritten POST as a forged request
|
|
CMD ["streamlit", "run", "app.py", \
|
|
"--server.port=8501", \
|
|
"--server.address=0.0.0.0", \
|
|
"--server.headless=true", \
|
|
"--server.enableCORS=false", \
|
|
"--server.enableXsrfProtection=false", \
|
|
"--browser.gatherUsageStats=false"]
|