chore: Tag Images with Version Number #2130#2175
Conversation
Greptile SummaryThis PR implements image version tagging for both the dev and production CI workflows (issue #2130). After each Docker Compose build-and-push, a new "Extract project version" step reads the version from Key observations:
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| .github/workflows/dev-build.yml | Adds version extraction (with null guard, character sanitization, and short-SHA suffix) and versioned image tagging/pushing after the existing dev build — but only checks api-server for duplicate tags, leaving the other four services unprotected against silent overwrites and blocking retries after partial failures. |
| .github/workflows/production-build.yml | Mirrors the dev workflow's versioned tagging additions for production, but is missing set -euo pipefail and the tr-based version sanitization present in the dev workflow, and shares the same partial-guard logic issue where only api-server is checked for tag existence. |
Sequence Diagram
sequenceDiagram
participant GHA as GitHub Actions
participant FS as Filesystem (package.json)
participant Docker as Docker Daemon
participant DHR as DockerHub Registry
GHA->>Docker: docker compose build (dev/prod)
GHA->>FS: jq -r .version package.json
FS-->>GHA: raw version string
GHA->>GHA: sanitize + form VERSION tag
GHA->>Docker: docker compose push (api-server, frontend, queue-consumer, documentation, artifact-uploader)
Docker->>DHR: push :api-server-dev / :api-server-latest (etc.)
GHA->>DHR: docker manifest inspect :api-server-VERSION
DHR-->>GHA: exists? yes → exit 1 / no → continue
GHA->>Docker: docker tag :api-server-dev → :api-server-VERSION
Docker->>DHR: docker push :api-server-VERSION
GHA->>Docker: docker tag :frontend-dev → :frontend-VERSION
Docker->>DHR: docker push :frontend-VERSION
GHA->>Docker: docker tag :queue-consumer-dev → :queue-consumer-VERSION
Docker->>DHR: docker push :queue-consumer-VERSION
GHA->>Docker: docker tag :docs-dev → :docs-VERSION
Docker->>DHR: docker push :docs-VERSION
GHA->>Docker: docker tag :artifact-uploader-dev → :artifact-uploader-VERSION
Docker->>DHR: docker push :artifact-uploader-VERSION
Prompt To Fix All With AI
This is a comment left during a code review.
Path: .github/workflows/dev-build.yml
Line: 46-59
Comment:
**Partial overwrite protection — only `api-server` is guarded**
The existence check only inspects `rslethz/kleinkram:api-server-${VERSION}`. The remaining four images (`frontend`, `queue-consumer`, `docs`, `artifact-uploader`) are tagged and pushed without any duplicate check.
Two concrete failure scenarios arise:
1. **Partial previous run** — if a prior workflow run tagged `api-server` and `frontend` but then failed, re-running will correctly error on the `api-server` check, making the run unrecoverable without bumping the version. No image can be retried.
2. **Stale tags for other services** — if `api-server-${VERSION}` does not yet exist but `frontend-${VERSION}` does (e.g. from a manual push), the check passes and the `frontend` image is silently overwritten.
Consider extending the guard to all five services:
```
for SERVICE in api-server frontend queue-consumer docs artifact-uploader; do
if docker manifest inspect rslethz/kleinkram:${SERVICE}-${VERSION} > /dev/null 2>&1; then
echo "ERROR: Version tag ${SERVICE}-${VERSION} already exists in registry." >&2
exit 1
fi
done
```
The same issue exists in `production-build.yml` lines 44–57.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: .github/workflows/production-build.yml
Line: 27-36
Comment:
**Missing `set -euo pipefail` vs. dev workflow**
The dev workflow's "Extract project version" step opens with `set -euo pipefail`, but the equivalent step in `production-build.yml` does not. Without `pipefail`, a failure in a sub-command that feeds into a variable assignment via `$()` can be silently masked (the assignment itself returns exit 0). Although the explicit null-check guard on lines 31–34 covers the most common failure mode, adding `set -euo pipefail` makes the script consistently defensive and brings parity with the dev workflow.
```suggestion
run: |
set -euo pipefail
VERSION="$(jq -r .version package.json)"
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: .github/workflows/production-build.yml
Line: 30-36
Comment:
**No version string sanitization for Docker tag compatibility**
The dev workflow sanitizes the raw version with `tr -c 'A-Za-z0-9_.-' '-'` before using it as a Docker tag, guarding against any non-standard characters in `package.json`. The production workflow only strips SemVer build metadata (`%%+*`) but does not apply the same sanitization.
Docker tags are restricted to `[a-zA-Z0-9_.-]`. While valid SemVer strings are already Docker-safe after stripping the `+` build segment, adding the same `tr` sanitization step would be a low-cost consistency improvement that prevents surprises if the version string ever contains unexpected characters.
```suggestion
VERSION_WITHOUT_BUILD="${VERSION%%+*}"
SAFE_VERSION="$(printf '%s' "$VERSION_WITHOUT_BUILD" | tr -c 'A-Za-z0-9_.-' '-')"
echo "version=v${SAFE_VERSION}" >> "$GITHUB_OUTPUT"
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 0592bbe
There was a problem hiding this comment.
Pull request overview
This PR updates the GitHub Actions Docker build workflows to additionally tag and push Docker images using the repository’s package.json version, producing versioned tags alongside the existing *-latest / *-dev tags.
Changes:
- Extracts the project version from
package.jsonduring CI runs. - Tags already-built images with
api-server-<version>,frontend-<version>, etc., and pushes those versioned tags to Docker Hub. - Applies the same behavior to both
main(production) anddevworkflows (with a-devsuffix).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| .github/workflows/production-build.yml | Adds version extraction and pushes additional version-tagged production images. |
| .github/workflows/dev-build.yml | Adds version extraction and pushes additional version-tagged dev images (version suffixed with -dev). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if docker manifest inspect rslethz/kleinkram:api-server-${VERSION} > /dev/null 2>&1; then | ||
| echo "ERROR: Version tag ${VERSION} already exists in registry." >&2 | ||
| exit 1 | ||
| fi | ||
| docker tag rslethz/kleinkram:api-server-dev rslethz/kleinkram:api-server-${VERSION} | ||
| docker push rslethz/kleinkram:api-server-${VERSION} | ||
| docker tag rslethz/kleinkram:frontend-dev rslethz/kleinkram:frontend-${VERSION} | ||
| docker push rslethz/kleinkram:frontend-${VERSION} | ||
| docker tag rslethz/kleinkram:queue-consumer-dev rslethz/kleinkram:queue-consumer-${VERSION} | ||
| docker push rslethz/kleinkram:queue-consumer-${VERSION} | ||
| docker tag rslethz/kleinkram:docs-dev rslethz/kleinkram:docs-${VERSION} | ||
| docker push rslethz/kleinkram:docs-${VERSION} | ||
| docker tag rslethz/kleinkram:artifact-uploader-dev rslethz/kleinkram:artifact-uploader-${VERSION} | ||
| docker push rslethz/kleinkram:artifact-uploader-${VERSION} |
There was a problem hiding this comment.
Partial overwrite protection — only api-server is guarded
The existence check only inspects rslethz/kleinkram:api-server-${VERSION}. The remaining four images (frontend, queue-consumer, docs, artifact-uploader) are tagged and pushed without any duplicate check.
Two concrete failure scenarios arise:
- Partial previous run — if a prior workflow run tagged
api-serverandfrontendbut then failed, re-running will correctly error on theapi-servercheck, making the run unrecoverable without bumping the version. No image can be retried. - Stale tags for other services — if
api-server-${VERSION}does not yet exist butfrontend-${VERSION}does (e.g. from a manual push), the check passes and thefrontendimage is silently overwritten.
Consider extending the guard to all five services:
for SERVICE in api-server frontend queue-consumer docs artifact-uploader; do
if docker manifest inspect rslethz/kleinkram:${SERVICE}-${VERSION} > /dev/null 2>&1; then
echo "ERROR: Version tag ${SERVICE}-${VERSION} already exists in registry." >&2
exit 1
fi
done
The same issue exists in production-build.yml lines 44–57.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/dev-build.yml
Line: 46-59
Comment:
**Partial overwrite protection — only `api-server` is guarded**
The existence check only inspects `rslethz/kleinkram:api-server-${VERSION}`. The remaining four images (`frontend`, `queue-consumer`, `docs`, `artifact-uploader`) are tagged and pushed without any duplicate check.
Two concrete failure scenarios arise:
1. **Partial previous run** — if a prior workflow run tagged `api-server` and `frontend` but then failed, re-running will correctly error on the `api-server` check, making the run unrecoverable without bumping the version. No image can be retried.
2. **Stale tags for other services** — if `api-server-${VERSION}` does not yet exist but `frontend-${VERSION}` does (e.g. from a manual push), the check passes and the `frontend` image is silently overwritten.
Consider extending the guard to all five services:
```
for SERVICE in api-server frontend queue-consumer docs artifact-uploader; do
if docker manifest inspect rslethz/kleinkram:${SERVICE}-${VERSION} > /dev/null 2>&1; then
echo "ERROR: Version tag ${SERVICE}-${VERSION} already exists in registry." >&2
exit 1
fi
done
```
The same issue exists in `production-build.yml` lines 44–57.
How can I resolve this? If you propose a fix, please make it concise.| - name: Extract project version | ||
| id: get_version | ||
| run: | | ||
| VERSION="$(jq -r .version package.json)" | ||
| if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then | ||
| echo "ERROR: Could not extract version from package.json" >&2 | ||
| exit 1 | ||
| fi | ||
| VERSION_WITHOUT_BUILD="${VERSION%%+*}" | ||
| echo "version=v${VERSION_WITHOUT_BUILD}" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
Missing set -euo pipefail vs. dev workflow
The dev workflow's "Extract project version" step opens with set -euo pipefail, but the equivalent step in production-build.yml does not. Without pipefail, a failure in a sub-command that feeds into a variable assignment via $() can be silently masked (the assignment itself returns exit 0). Although the explicit null-check guard on lines 31–34 covers the most common failure mode, adding set -euo pipefail makes the script consistently defensive and brings parity with the dev workflow.
| - name: Extract project version | |
| id: get_version | |
| run: | | |
| VERSION="$(jq -r .version package.json)" | |
| if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then | |
| echo "ERROR: Could not extract version from package.json" >&2 | |
| exit 1 | |
| fi | |
| VERSION_WITHOUT_BUILD="${VERSION%%+*}" | |
| echo "version=v${VERSION_WITHOUT_BUILD}" >> "$GITHUB_OUTPUT" | |
| run: | | |
| set -euo pipefail | |
| VERSION="$(jq -r .version package.json)" | |
| if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then |
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/production-build.yml
Line: 27-36
Comment:
**Missing `set -euo pipefail` vs. dev workflow**
The dev workflow's "Extract project version" step opens with `set -euo pipefail`, but the equivalent step in `production-build.yml` does not. Without `pipefail`, a failure in a sub-command that feeds into a variable assignment via `$()` can be silently masked (the assignment itself returns exit 0). Although the explicit null-check guard on lines 31–34 covers the most common failure mode, adding `set -euo pipefail` makes the script consistently defensive and brings parity with the dev workflow.
```suggestion
run: |
set -euo pipefail
VERSION="$(jq -r .version package.json)"
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
```
How can I resolve this? If you propose a fix, please make it concise.| VERSION="$(jq -r .version package.json)" | ||
| if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then | ||
| echo "ERROR: Could not extract version from package.json" >&2 | ||
| exit 1 | ||
| fi | ||
| VERSION_WITHOUT_BUILD="${VERSION%%+*}" | ||
| echo "version=v${VERSION_WITHOUT_BUILD}" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
No version string sanitization for Docker tag compatibility
The dev workflow sanitizes the raw version with tr -c 'A-Za-z0-9_.-' '-' before using it as a Docker tag, guarding against any non-standard characters in package.json. The production workflow only strips SemVer build metadata (%%+*) but does not apply the same sanitization.
Docker tags are restricted to [a-zA-Z0-9_.-]. While valid SemVer strings are already Docker-safe after stripping the + build segment, adding the same tr sanitization step would be a low-cost consistency improvement that prevents surprises if the version string ever contains unexpected characters.
| VERSION="$(jq -r .version package.json)" | |
| if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then | |
| echo "ERROR: Could not extract version from package.json" >&2 | |
| exit 1 | |
| fi | |
| VERSION_WITHOUT_BUILD="${VERSION%%+*}" | |
| echo "version=v${VERSION_WITHOUT_BUILD}" >> "$GITHUB_OUTPUT" | |
| VERSION_WITHOUT_BUILD="${VERSION%%+*}" | |
| SAFE_VERSION="$(printf '%s' "$VERSION_WITHOUT_BUILD" | tr -c 'A-Za-z0-9_.-' '-')" | |
| echo "version=v${SAFE_VERSION}" >> "$GITHUB_OUTPUT" |
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/production-build.yml
Line: 30-36
Comment:
**No version string sanitization for Docker tag compatibility**
The dev workflow sanitizes the raw version with `tr -c 'A-Za-z0-9_.-' '-'` before using it as a Docker tag, guarding against any non-standard characters in `package.json`. The production workflow only strips SemVer build metadata (`%%+*`) but does not apply the same sanitization.
Docker tags are restricted to `[a-zA-Z0-9_.-]`. While valid SemVer strings are already Docker-safe after stripping the `+` build segment, adding the same `tr` sanitization step would be a low-cost consistency improvement that prevents surprises if the version string ever contains unexpected characters.
```suggestion
VERSION_WITHOUT_BUILD="${VERSION%%+*}"
SAFE_VERSION="$(printf '%s' "$VERSION_WITHOUT_BUILD" | tr -c 'A-Za-z0-9_.-' '-')"
echo "version=v${SAFE_VERSION}" >> "$GITHUB_OUTPUT"
```
How can I resolve this? If you propose a fix, please make it concise.
No description provided.