Skip to content

feat(docker): bake git provenance into image via build-args + export from docker-run#866

Merged
tcsenpai merged 1 commit into
stabilisationfrom
feat/docker-commit-arg
May 26, 2026
Merged

feat(docker): bake git provenance into image via build-args + export from docker-run#866
tcsenpai merged 1 commit into
stabilisationfrom
feat/docker-commit-arg

Conversation

@tcsenpai
Copy link
Copy Markdown
Contributor

Summary

Closes the gap left by #864 + #865. src/utilities/nodeVersion.ts already reads process.env.GIT_COMMIT/GIT_BRANCH/GIT_DIRTY when present, but nothing on the Docker path was supplying them — so getNetworkInfo.nodeVersion came back with commit: null on every Docker-booted node, defeating the diagnostic value of the whole endpoint.

Three pieces

  1. Dockerfile (runtime stage): ARG GIT_COMMIT/GIT_BRANCH/GIT_DIRTY/BUILT_AT, re-exported via ENV so the running node sees them in process.env. Empty-string defaults — a manual docker build . without args still produces a runnable image; nodeVersion surfaces null for the missing fields.
  2. docker-compose.yml (node service build.args): passes the four values through with :- defaults. Any caller that exported them gets them baked in; anyone who didn't gets the null fallback.
  3. scripts/docker-run: detects the working tree's git status defensively (no-repo, no-git-binary, dirty-vs-clean) and exports the four vars before docker compose build/up. git diff-index --quiet HEAD exit → "true"/"false" string the node module parses. BUILT_AT is the UTC ISO-8601 timestamp at invocation.

Operator-visible effect

After ./scripts/docker-run --rebuild -d:

curl -sS -X POST -H 'Content-Type: application/json' \
     -d '{"method":"nodeCall","params":[{"type":"nodeCall","message":"getNetworkInfo","data":{}}]}' \
     http://your-node-rpc/ | jq .response.nodeVersion
{
  "name": "demos-node-software",
  "version": "0.9.8",
  "commit": "<actual SHA>",
  "commitShort": "abc1234",
  "branch": "stabilisation",
  "dirty": false,
  "builtAt": "2026-05-26T14:55:00Z"
}

Answers "did this host actually pick up the fix I merged?" in one line.

Manual override paths still work

docker compose build --build-arg GIT_COMMIT=$(git rev-parse HEAD)
GIT_COMMIT=abc123 docker compose build

Test plan

  • docker-run script: hand-traced env export logic; defensive fallbacks for no-repo / no-git/binary
  • Verify in production by ./scripts/docker-run --rebuild -d on dev.node2 + curl'ing getNetworkInfo again

…from docker-run

Closes the gap left by PR #864/#865: src/utilities/nodeVersion.ts
already reads process.env.GIT_COMMIT/GIT_BRANCH/GIT_DIRTY when present,
but nothing on the docker path was supplying them. The result was that
getNetworkInfo.nodeVersion came back with commit: null on every Docker-
booted node, defeating the original "is this host running the binary I
think it is?" diagnostic value.

Three pieces wire it end-to-end:

1) Dockerfile (runtime stage): ARG GIT_COMMIT/GIT_BRANCH/GIT_DIRTY/
   BUILT_AT, then re-export each via ENV so the running node sees them
   in process.env. Defaults are empty strings — a build without the
   args (manual `docker build .`) still produces a runnable image,
   nodeVersion just surfaces null for the missing fields.

2) docker-compose.yml (node service build block): args passes the
   four values straight through with `:-` defaults, so any caller
   that exported them (the wrapper below, CI, an operator's shell)
   gets them baked in; anyone who didn't gets an empty-string ARG
   and the same null-field fallback.

3) scripts/docker-run: detects the working tree's git status with
   defensive guards (no-repo, no-git-binary, dirty-vs-clean) and
   exports the four variables before any `docker compose build`/`up`.
   `git diff-index --quiet HEAD` exit code → "true"/"false" string the
   nodeVersion module already parses. BUILT_AT is the UTC ISO-8601
   timestamp at invocation.

Operator-visible effect: after `./scripts/docker-run --rebuild -d`,
curl getNetworkInfo and the response carries the real commit SHA,
branch name, dirty flag, and build timestamp the image was made from.
That answers "did this host actually pick up the fix I merged?" with
a one-liner.

Manual repro paths still work:
  docker compose build --build-arg GIT_COMMIT=$(git rev-parse HEAD)
  GIT_COMMIT=abc123 docker compose build
@qodo-code-review
Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@tcsenpai tcsenpai merged commit 0f8ead5 into stabilisation May 26, 2026
1 of 2 checks passed
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

@tcsenpai, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 28 minutes and 57 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 93a9a85d-6a7d-4a5d-ba5d-21679ee71e42

📥 Commits

Reviewing files that changed from the base of the PR and between 0c228ab and 0a9ed52.

📒 Files selected for processing (3)
  • Dockerfile
  • docker-compose.yml
  • scripts/docker-run
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/docker-commit-arg

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tcsenpai tcsenpai deleted the feat/docker-commit-arg branch May 26, 2026 14:50
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 26, 2026

Greptile Summary

This PR closes the gap where Docker-booted nodes always returned commit: null from getNetworkInfo.nodeVersion by baking git provenance into the image at build time via ARG/ENV in the Dockerfile, build.args in docker-compose.yml, and a new git-detection block in scripts/docker-run.

  • Dockerfile: Declares ARG GIT_COMMIT/GIT_BRANCH/GIT_DIRTY/BUILT_AT with safe empty/false defaults, then re-exports them as ENV so src/utilities/nodeVersion.ts can read them from process.env without shipping .git/ into the runtime layer.
  • docker-compose.yml: Adds build.args with :- shell defaults so both the wrapper script path and manual docker compose build invocations work seamlessly.
  • scripts/docker-run: Detects repo state defensively (no-repo, no-git-binary, dirty-vs-clean) and exports the four variables before the compose build step.

Confidence Score: 4/5

Safe to merge; the changes are additive and all fallback paths produce a runnable image with null provenance fields rather than a broken build.

The Dockerfile and Compose changes are straightforward and well-guarded. The docker-run script has a small redundancy (recomputing the repo root that already exists as REPO_ROOT) and a behaviour quirk where CI checkouts in detached HEAD state will export HEAD as the branch name rather than null, which surfaces verbatim in the getNetworkInfo API response and could confuse operators trying to identify the source branch of a running node.

scripts/docker-run — the detached HEAD branch-name behaviour and the GIT_REPO_DIR redundancy are both in the new provenance block.

Important Files Changed

Filename Overview
Dockerfile Adds ARG declarations for GIT_COMMIT, GIT_BRANCH, GIT_DIRTY, BUILT_AT and re-exports them as ENV vars in the runtime stage; ARGs are correctly placed before the ENV block that consumes them.
docker-compose.yml Adds build.args block with appropriate :- defaults; no structural changes to services or volumes.
scripts/docker-run New git provenance block exports four vars before compose build; two minor issues: GIT_REPO_DIR redundantly recomputes REPO_ROOT, and detached HEAD exports "HEAD" as branch name rather than null.

Sequence Diagram

sequenceDiagram
    participant Host as Host Shell
    participant Script as scripts/docker-run
    participant Git as git CLI
    participant Compose as docker compose
    participant Dockerfile as Dockerfile (runtime stage)
    participant Node as Node Process

    Host->>Script: ./scripts/docker-run --rebuild -d
    Script->>Git: rev-parse HEAD
    Git-->>Script: GIT_COMMIT (40-char SHA)
    Script->>Git: rev-parse --abbrev-ref HEAD
    Git-->>Script: GIT_BRANCH (or HEAD if detached)
    Script->>Git: diff-index --quiet HEAD
    Git-->>Script: exit 0 clean / 1 dirty
    Script->>Script: export GIT_COMMIT GIT_BRANCH GIT_DIRTY BUILT_AT
    Script->>Compose: docker compose build --no-cache node
    Compose->>Dockerfile: --build-arg GIT_COMMIT GIT_BRANCH GIT_DIRTY BUILT_AT
    Dockerfile->>Dockerfile: ARG to ENV baked into image layer
    Compose->>Node: docker compose up
    Node->>Node: nodeVersion.ts reads env vars
Loading

Reviews (1): Last reviewed commit: "feat(docker): bake git provenance into i..." | Re-trigger Greptile

Comment thread scripts/docker-run
Comment on lines +138 to +147
if git -C "$(dirname "${BASH_SOURCE[0]}")/.." rev-parse --git-dir >/dev/null 2>&1; then
GIT_REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export GIT_COMMIT="$(git -C "$GIT_REPO_DIR" rev-parse HEAD 2>/dev/null || true)"
export GIT_BRANCH="$(git -C "$GIT_REPO_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
if git -C "$GIT_REPO_DIR" diff-index --quiet HEAD 2>/dev/null; then
export GIT_DIRTY="false"
else
export GIT_DIRTY="true"
fi
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The block recomputes the repository root using ${BASH_SOURCE[0]} when REPO_ROOT has already been resolved to the exact same path (via $0) at line 52 of the script. The two expansions resolve identically for a directly-executed script. Using REPO_ROOT directly removes the redundant subshell and keeps one canonical root variable throughout the script.

Suggested change
if git -C "$(dirname "${BASH_SOURCE[0]}")/.." rev-parse --git-dir >/dev/null 2>&1; then
GIT_REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export GIT_COMMIT="$(git -C "$GIT_REPO_DIR" rev-parse HEAD 2>/dev/null || true)"
export GIT_BRANCH="$(git -C "$GIT_REPO_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
if git -C "$GIT_REPO_DIR" diff-index --quiet HEAD 2>/dev/null; then
export GIT_DIRTY="false"
else
export GIT_DIRTY="true"
fi
fi
if git -C "$REPO_ROOT" rev-parse --git-dir >/dev/null 2>&1; then
export GIT_COMMIT="$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || true)"
export GIT_BRANCH="$(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
if git -C "$REPO_ROOT" diff-index --quiet HEAD 2>/dev/null; then
export GIT_DIRTY="false"
else
export GIT_DIRTY="true"
fi
fi

Comment thread scripts/docker-run
if git -C "$(dirname "${BASH_SOURCE[0]}")/.." rev-parse --git-dir >/dev/null 2>&1; then
GIT_REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export GIT_COMMIT="$(git -C "$GIT_REPO_DIR" rev-parse HEAD 2>/dev/null || true)"
export GIT_BRANCH="$(git -C "$GIT_REPO_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Detached HEAD produces "HEAD" as branch name in API output

git rev-parse --abbrev-ref HEAD returns the literal string "HEAD" when the repo is in a detached HEAD state — which is the norm for GitHub Actions, GitLab CI, and most CI/CD checkouts for PRs or tag builds. That string passes the || null guard in nodeVersion.ts ("HEAD" is truthy), so the getNetworkInfo response surfaces "branch": "HEAD" instead of null, which is indistinguishable from an actual branch named HEAD and offers no useful diagnostic info. Consider filtering the value before export: [[ "$GIT_BRANCH" == "HEAD" ]] && unset GIT_BRANCH (or export empty string) to let the module fall through to null.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant