Identify the build by a source fingerprint, not a commit SHA
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).
This commit is contained in:
parent
7fd221288a
commit
772ba7fa33
5 changed files with 26 additions and 27 deletions
17
Dockerfile
17
Dockerfile
|
|
@ -19,11 +19,6 @@ ENV PYTHONUNBUFFERED=1 \
|
|||
|
||||
WORKDIR /app
|
||||
|
||||
# Which build is this? Read from a file IN the repo rather than a build arg,
|
||||
# because Coolify does not pass a commit SHA into a Docker Compose build - an
|
||||
# ARG would silently fall back to "dev" forever and the marker would never
|
||||
# tell you anything. Docker's ENV also does not evaluate $(cat ...), so the
|
||||
# file is simply copied and the app reads it at startup.
|
||||
|
||||
# Dependencies first, so a code change does not reinstall the world.
|
||||
COPY requirements.txt .
|
||||
|
|
@ -33,12 +28,22 @@ COPY app.py run.py ./
|
|||
COPY boq_engine/ ./boq_engine/
|
||||
COPY static/ ./static/
|
||||
COPY .streamlit/ ./.streamlit/
|
||||
COPY build.txt ./build.txt
|
||||
|
||||
# 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 \
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -8,12 +8,15 @@ disagree.
|
|||
Anything you disagree with, you settle **in the app** and regenerate. The
|
||||
consultant never has to leave the screen.
|
||||
|
||||
Runs entirely on your machine. Nothing is uploaded anywhere. The sidebar shows
|
||||
the build SHA, so you can always see which version you are looking at. It is
|
||||
read from **`build.txt` in the repo**, not a build argument, because Coolify
|
||||
does not pass a commit SHA into a Docker Compose build — an `ARG` silently
|
||||
falls back to `dev` forever and the marker then tells you nothing.
|
||||
`./update_build_marker.sh` rewrites it; run it as part of committing.
|
||||
Runs entirely on your machine. Nothing is uploaded anywhere. The sidebar shows a
|
||||
**build fingerprint** — a hash of the `.py`/`.css`/`.toml` actually copied into
|
||||
the image, computed during the Docker build. It reads e.g. `build d7d427a2e770`.
|
||||
|
||||
A commit SHA was the obvious first choice and was wrong twice over: Coolify
|
||||
does not pass a commit SHA into a Docker Compose build, so an `ARG` silently
|
||||
falls back to `dev` forever; and a file naming the commit cannot live in the
|
||||
commit it names, so it could only ever name the parent. A hash of the shipped
|
||||
files is true by construction and cannot go stale.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
13
app.py
13
app.py
|
|
@ -111,13 +111,12 @@ def _fp(data: bytes) -> str:
|
|||
|
||||
|
||||
def build_sha() -> str:
|
||||
"""Which build is this? Shown in the sidebar so a deploy can be confirmed
|
||||
without digging through Coolify's log.
|
||||
"""Fingerprint of the source in this image, shown in the sidebar.
|
||||
|
||||
Falls back through several routes because Streamlit executes this file in
|
||||
an exec context where __file__ is not guaranteed - a bare reference to it
|
||||
would take the whole app down. Falls back to the env var, then to a
|
||||
literal, and never raises.
|
||||
Computed at image build time over the .py/.css/.toml actually copied in, so
|
||||
it identifies the running code by construction. Falls back through several
|
||||
paths because Streamlit execs this file where __file__ is not guaranteed -
|
||||
a bare reference to it would take the whole app down - and never raises.
|
||||
"""
|
||||
candidates = []
|
||||
try:
|
||||
|
|
@ -135,7 +134,7 @@ def build_sha() -> str:
|
|||
return val
|
||||
except OSError:
|
||||
continue
|
||||
return os.environ.get("BOQ_BUILD_SHA") or "dev"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def money(v) -> str:
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
bcba46a
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Rewrite build.txt to the current commit. Run as part of committing, so the
|
||||
# marker in the sidebar always names the build that is actually deployed.
|
||||
# Wired up as a git post-commit hook when the repo is set up locally.
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
git rev-parse --short HEAD > build.txt
|
||||
Loading…
Reference in a new issue