Skip to content

chore: Tag Images with Version Number #2130#2175

Open
wp99cp wants to merge 3 commits into
devfrom
feat/image-tags
Open

chore: Tag Images with Version Number #2130#2175
wp99cp wants to merge 3 commits into
devfrom
feat/image-tags

Conversation

@wp99cp

@wp99cp wp99cp commented Mar 16, 2026

Copy link
Copy Markdown
Member

No description provided.

Copilot AI review requested due to automatic review settings March 16, 2026 07:37
@greptile-apps

greptile-apps Bot commented Mar 16, 2026

Copy link
Copy Markdown

Greptile Summary

This 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 package.json, validates it, and forms a structured tag (v{version}-dev-{short-sha} for dev, v{version} for production). A subsequent step then retags the five built images under those versioned names and pushes them to DockerHub alongside the rolling dev/latest tags.

Key observations:

  • Both previous review concerns (silent null version and unconditional overwrite) have been addressed — null/empty guards are present and a docker manifest inspect check blocks re-pushing an existing version.
  • Partial overwrite protection: the duplicate-tag check covers only api-server; the other four services (frontend, queue-consumer, docs, artifact-uploader) are tagged and pushed without any existence check, allowing silent overwrites and making partial-failure retries impossible without a version bump.
  • Inconsistency between workflows: production-build.yml is missing set -euo pipefail and the tr-based character sanitization that are present in dev-build.yml.
  • The documentation compose service correctly maps to the docs-dev / docs-latest image names as confirmed in the docker-compose files.

Confidence Score: 3/5

  • Safe to merge with caveats — the core versioning logic is sound, but the partial duplicate-tag guard introduces a retry trap worth fixing before this ships to production.
  • The previous two blocking concerns (null version and silent overwrite) have been addressed. However, only the api-server image is checked for tag uniqueness; the other four images remain unguarded, which can silently overwrite them and makes partial-failure recovery impossible without bumping the version. There are also minor consistency gaps between the dev and production scripts (missing set -euo pipefail and tr sanitization in production). These are not showstoppers but are real operational risks for a CI pipeline managing immutable release tags.
  • Both .github/workflows/dev-build.yml and .github/workflows/production-build.yml — specifically the "Tag and push versioned images" steps — need attention to extend the duplicate-tag guard to all five services.

Important Files Changed

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
Loading
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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.json during 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) and dev workflows (with a -dev suffix).

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.

Comment thread .github/workflows/dev-build.yml Outdated
Comment thread .github/workflows/production-build.yml Outdated
Comment thread .github/workflows/production-build.yml Outdated
Comment thread .github/workflows/dev-build.yml Outdated
Comment thread .github/workflows/dev-build.yml Outdated
Comment thread .github/workflows/dev-build.yml Outdated
Comment thread .github/workflows/dev-build.yml Outdated
Comment on lines +46 to +59
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}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

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.

Comment on lines +27 to +36
- 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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Suggested change
- 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.

Comment on lines +30 to +36
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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Suggested change
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.

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.

2 participants