Fix: a docker source build reported "dev", not its version - #125
Merged
Conversation
`backend/Dockerfile` had `ARG APP_VERSION=dev` feeding `ENV
APP_VERSION=${APP_VERSION}`. A Dockerfile has no conditional ENV, so that
line always ran — and pydantic-settings ranks an environment variable
above the Python default. `ENV APP_VERSION=dev` therefore shadowed
config.py's "1.2.0-src" on every `docker compose up --build`, which is the
README's headline install path. Every source deployment reported "dev",
which also meant none of them could ever be told a new release existed
("dev" doesn't parse, so is_newer always returned False).
The recent change from "dev" to "1.2.0-src" only moved the Python default;
it never touched the env var that was overriding it, so it fixed nothing
for the path that matters.
ARG now defaults to **empty** — empty means "not a release image" — and a
validator resolves blank to SOURCE_BUILD_VERSION. Blank includes
whitespace: a quoted compose value or a heredoc newline would otherwise
reach the update endpoint and be bucketed as unparseable.
The default also moves out of the field and into a module constant, so
there's one name to grep, bump and assert on rather than a literal buried
in a settings class.
Two guards, both on the *mechanism* rather than the value — the value read
correctly the whole time, only the interaction was wrong:
- a test asserting `ARG APP_VERSION` carries no literal default;
- a second tag-push CI step doing the same, so a regression blocks the
release rather than shipping.
Why this got through: the config default was tested by importing Settings
in isolation, where no env var exists, and the existing CI guard only read
config.py. Both agreed on "1.2.0-src". Neither exercised a container. A
single `docker compose up --build` would have caught it.
Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
A fresh
git clone+docker compose up --buildreported its version asdev, not1.2.0-src— the README's headline install path, so most deployments.backend/Dockerfilehad:A Dockerfile has no conditional
ENV, so that line always runs — and pydantic-settings ranks an environment variable above the Python default.ENV APP_VERSION=devtherefore shadowedconfig.py's"1.2.0-src"on every source build.Two consequences, the second worse than the first:
devbucket with no version granularity."dev"doesn't parse, sois_newer()always returnedFalse.#124 changed the Python default from
"dev"to"1.2.0-src"but never touched the env var overriding it, so it fixed nothing for the path that matters.The fix
ARG APP_VERSION=— empty, meaning "not a release image" — with a validator inconfig.pyresolving blank toSOURCE_BUILD_VERSION. Blank includes whitespace: a quoted compose value or a heredoc newline would otherwise reach the update endpoint and be bucketed asinvalid.Verified across every path:
APP_VERSIONuvicorn)1.2.0-src""(source docker build)1.2.0-src" "(whitespace)1.2.0-src" v1.2.0 \n"(stray newline)v1.2.0v1.2.0(release image)v1.2.0The default also moves out of the settings field into a module constant, so there's one name to grep, bump and assert on rather than a literal buried in a class.
Guards on the mechanism, not the value
The value read
1.2.0-srccorrectly the whole time — only the interaction was wrong. So both new guards check the mechanism:ARG APP_VERSIONcarries no literal default;Confirmed both fire: restoring
ARG APP_VERSION=devblocks.How this got through
Worth recording, because the gap was in the testing approach rather than the code. The config default was tested by importing
Settingsin isolation, where no env var exists, and the existing CI guard only readconfig.py. Both agreed on1.2.0-src. Neither exercised a container. A singledocker compose up --buildwould have caught it in seconds — and that's the check that was missing when the version-resolution mechanism changed.Checklist
cd backend && .venv/bin/pytest(569 passed, 2 new)After merging, the useful check is a real one: pull,
docker compose up --build, and confirm Admin → Site settings → General reads "Running version 1.2.0-src". A running container keeps reportingdevuntil rebuilt, since the value is baked at image build time.🤖 Generated with Claude Code