Skip to content

fix(docker): enable build cache with per-platform registry cache#23

Open
bskim45 wants to merge 1 commit intomainfrom
ci/docker-cache
Open

fix(docker): enable build cache with per-platform registry cache#23
bskim45 wants to merge 1 commit intomainfrom
ci/docker-cache

Conversation

@bskim45
Copy link
Copy Markdown
Owner

@bskim45 bskim45 commented Apr 2, 2026

Summary

  • Root cause: ARG VERSION/REVISIONpython-base 스테이지에 선언되어 매 커밋마다 모든 하위 레이어(uv sync, playwright install)가 캐시 무효화됨
  • ARG/LABEL을 최종 runtime 스테이지로 이동하여 중간 스테이지 캐시 보존
  • 멀티플랫폼 빌드를 matrix strategy로 분리하고 GHCR registry cache 적용
  • merge job 추가로 플랫폼별 digest를 멀티플랫폼 manifest로 합침

Test plan

Summary by CodeRabbit

  • Chores
    • Optimized Docker multi-platform build and release process for improved efficiency
    • Improved container image metadata organization

Root cause: ARG VERSION/REVISION were declared in the python-base stage,
causing all downstream layers (build-venv, build-playwright) to be
invalidated on every commit since REVISION changes with each git SHA.

Changes:
- Move ARG/LABEL to the final runtime stage so intermediate stages
  (uv sync ~1s, playwright install ~150s) are properly cached
- Split multi-platform build into matrix strategy (amd64/arm64)
  with per-platform registry cache stored in GHCR
- Add merge job to create multi-platform manifests via imagetools

Result: cached builds complete in ~35s vs ~230s (arm64), 84% faster.
Copilot AI review requested due to automatic review settings April 2, 2026 15:46
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request relocates build arguments and image labels from the base stage to the final runtime stage in the Dockerfile. A suggestion was made to include the REVISION argument within the image labels to improve traceability.

Comment thread Dockerfile
Comment on lines +48 to +52
LABEL org.opencontainers.image.title="ridiwise" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.url="https://github.com/bskim45/ridiwise" \
org.opencontainers.image.source="https://github.com/bskim45/ridiwise" \
org.opencontainers.image.authors="Bumsoo Kim <bskim45@gmail.com>"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

REVISION 인자가 정의되어 있고 빌드 시 전달되고 있지만, 이미지 메타데이터인 LABEL에는 포함되어 있지 않습니다. 이미지의 추적성(traceability)을 위해 org.opencontainers.image.revision 라벨을 추가하는 것을 권장합니다.

LABEL org.opencontainers.image.title="ridiwise" \
    org.opencontainers.image.version="${VERSION}" \
    org.opencontainers.image.revision="${REVISION}" \
    org.opencontainers.image.url="https://github.com/bskim45/ridiwise" \
    org.opencontainers.image.source="https://github.com/bskim45/ridiwise" \
    org.opencontainers.image.authors="Bumsoo Kim <bskim45@gmail.com>"

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

Walkthrough

The Docker release workflow transitions from a single multi-platform build to a matrix-based strategy that builds each platform separately and then merges digests into multi-architecture manifest lists. Dockerfile metadata labels and build arguments shift from the python-base stage to the runtime stage.

Changes

Cohort / File(s) Summary
Docker Release Workflow
.github/workflows/release_docker.yml
Restructured to use a matrix job for per-platform builds (linux/amd64, linux/arm64) with digest capture, followed by a new merge job that creates and pushes multi-architecture manifest lists. Build caching moved from GitHub Actions to registry-based keying.
Dockerfile Metadata Relocation
Dockerfile
Moved ARG VERSION, ARG REVISION, and all org.opencontainers.image.* labels from the python-base stage to the runtime stage. Removed ARG UID=1001 from python-base. Added minor whitespace.

Sequence Diagram(s)

sequenceDiagram
    participant GH as GitHub Actions
    participant M as Matrix Job (per platform)
    participant R as Registry
    participant MJ as Merge Job
    
    GH->>M: Trigger matrix builds (amd64, arm64)
    par Platform Builds
        M->>R: Build & push linux/amd64 by digest
        M->>R: Build & push linux/arm64 by digest
    end
    M->>GH: Upload digest artifacts
    GH->>MJ: All builds complete, trigger merge
    MJ->>GH: Download all digest artifacts
    MJ->>R: Create multi-arch manifest (docker buildx imagetools)
    MJ->>R: Push manifest lists to Docker Hub & GHCR
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: enabling build cache with per-platform registry cache. It directly reflects the PR's core objective of improving Docker build caching through architectural changes to the workflow and Dockerfile.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/docker-cache

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.

Copy link
Copy Markdown

Copilot AI left a comment

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 improves Docker image build performance and reliability by preserving intermediate layer cache across commits and by switching the release workflow to a per-platform registry cache + manifest merge approach for multi-arch publishing.

Changes:

  • Move ARG VERSION/REVISION and OCI LABELs to the final runtime stage so earlier stages (deps/playwright) remain cacheable.
  • Split multi-platform release builds into a matrix (amd64/arm64) with per-platform GHCR registry cache.
  • Add a merge job that assembles per-platform digests into multi-platform manifests for Docker Hub and GHCR.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
Dockerfile Moves version/revision build args + labels to final stage to avoid cache invalidation of dependency layers.
.github/workflows/release_docker.yml Switches to per-platform matrix builds with registry cache and merges digests into multi-arch manifests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +154 to +157
# Create and push manifest for GHCR
docker buildx imagetools create \
$(jq -cr '.tags | map(select(startswith("ghcr.io")) | "-t " + .) | join(" ")' <<< "$DOCKER_META_JSON") \
$(printf "${GHCR_IMAGE}@sha256:%s " *)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

위 Docker Hub manifest 생성과 동일하게, GHCR 쪽 jq 필터에도 \" 이 포함되어 있어 쉘/ jq 파싱 오류를 유발할 수 있습니다. single-quote 안에서는 double quote를 이스케이프할 필요가 없으므로 backslash를 제거해 jq 필터가 정상 파싱되도록 수정해주세요.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to 82
platforms: ${{ matrix.platform }}
outputs: type=image,"name=${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }},${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}",push-by-digest=true,name-canonical=true,push=true
build-args: |
VERSION=${{ fromJSON(steps.docker-meta.outputs.json).labels['org.opencontainers.image.version'] }}
REVISION=${{ fromJSON(steps.docker-meta.outputs.json).labels['org.opencontainers.image.revision'] }}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

outputs: 값에 \"(역슬래시+따옴표)가 들어가 있는데, YAML plain scalar에서는 이스케이프가 처리되지 않아 역슬래시가 그대로 buildx로 전달됩니다. 이 경우 --output type=image,\"name=...\" 형태가 되어 output 파싱/이미지 이름 파싱이 실패할 수 있습니다. 역슬래시를 제거하고(필요하면 YAML 값 전체를 single/double quote로 감싸서) buildx가 기대하는 type=image,"name=..."... / type=image,name=... 형태로 전달되도록 수정해주세요.

Copilot uses AI. Check for mistakes.
Comment on lines +149 to +152
# Create and push manifest for Docker Hub
docker buildx imagetools create \
$(jq -cr '.tags | map(select(startswith("docker.io")) | "-t " + .) | join(" ")' <<< "$DOCKER_META_JSON") \
$(printf "${DOCKERHUB_IMAGE}@sha256:%s " *)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

jq -cr '...' 필터 문자열은 single quote로 감싸여 있으므로 내부의 double quote(")를 백슬래시로 이스케이프할 필요가 없습니다. 현재처럼 \"가 들어가면 쉘이 이를 처리하지 않고 jq에 그대로 전달되어 필터 파싱이 실패할 수 있습니다. startswith(\"docker.io\") / \"-t \" 처럼 백슬래시가 붙은 부분에서 백슬래시를 제거해 주세요.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/release_docker.yml (1)

31-31: ⚠️ Potential issue | 🟠 Major

Update GitHub Actions to current stable versions.

Several actions are pinned to outdated versions:

  • docker/setup-qemu-action@v3 → v4.0.0
  • docker/setup-buildx-action@v3 → v4.0.0
  • docker/metadata-action@v5 → v6.0.0
  • docker/build-push-action@v6 → v7.0.0
  • actions/upload-artifact@v4 → v7.0.0
  • actions/download-artifact@v4 → v8.0.1

Current versions include recent improvements (e.g., Node 24 runtime for Docker actions, direct upload/download without zipping for artifact actions).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release_docker.yml at line 31, Update the GitHub Actions
steps to use the current stable major versions by changing the action versions
referenced in the workflow: bump docker/setup-qemu-action from v3 to v4.0.0,
docker/setup-buildx-action from v3 to v4.0.0, docker/metadata-action from v5 to
v6.0.0, docker/build-push-action from v6 to v7.0.0, actions/upload-artifact from
v4 to v7.0.0, and actions/download-artifact from v4 to v8.0.1; ensure each uses
line items that reference those action names (e.g., uses:
docker/setup-qemu-action, docker/setup-buildx-action, docker/metadata-action,
docker/build-push-action, actions/upload-artifact, actions/download-artifact) so
the workflow picks up the updated releases and their runtime improvements.
🧹 Nitpick comments (1)
.github/workflows/release_docker.yml (1)

142-157: Manifest creation logic is correct, but consider edge case handling.

The jq filtering and printf glob expansion correctly build the imagetools create commands. However, if no tags match the filter (e.g., startswith("docker.io") returns empty), the command would run without any -t flags, which may cause unexpected behavior.

Given that type=edge always generates tags for pushes to main, this is unlikely to occur in practice, but you might want to add a guard or explicit tag validation for robustness.

💡 Optional: Add tag validation
         run: |
+          DOCKERHUB_TAGS=$(jq -cr '.tags | map(select(startswith("docker.io")) | "-t " + .) | join(" ")' <<< "$DOCKER_META_JSON")
+          GHCR_TAGS=$(jq -cr '.tags | map(select(startswith("ghcr.io")) | "-t " + .) | join(" ")' <<< "$DOCKER_META_JSON")
+          
+          if [ -z "$DOCKERHUB_TAGS" ] || [ -z "$GHCR_TAGS" ]; then
+            echo "Error: No tags generated for one or more registries"
+            exit 1
+          fi
+
           # Create and push manifest for Docker Hub
           docker buildx imagetools create \
-            $(jq -cr '.tags | map(select(startswith("docker.io")) | "-t " + .) | join(" ")' <<< "$DOCKER_META_JSON") \
+            $DOCKERHUB_TAGS \
             $(printf "${DOCKERHUB_IMAGE}@sha256:%s " *)

           # Create and push manifest for GHCR
           docker buildx imagetools create \
-            $(jq -cr '.tags | map(select(startswith("ghcr.io")) | "-t " + .) | join(" ")' <<< "$DOCKER_META_JSON") \
+            $GHCR_TAGS \
             $(printf "${GHCR_IMAGE}@sha256:%s " *)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release_docker.yml around lines 142 - 157, The imagetools
create steps may run with no -t flags when jq returns no matching tags; before
invoking the docker buildx imagetools create commands (the blocks that use jq
-cr '.tags | map(select(startswith("docker.io")) ...)' and the printf
"${DOCKERHUB_IMAGE}@sha256:%s " * for GHCR), add a guard that validates tag
lists from DOCKER_META_JSON (and the glob expansion) are non-empty and skip or
fail fast if empty; specifically, capture jq output into a variable (for
docker.io and ghcr.io), check its length, and only call docker buildx imagetools
create when that variable (and the corresponding printf/glob result) contains
entries to avoid invoking imagetools with no -t flags.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In @.github/workflows/release_docker.yml:
- Line 31: Update the GitHub Actions steps to use the current stable major
versions by changing the action versions referenced in the workflow: bump
docker/setup-qemu-action from v3 to v4.0.0, docker/setup-buildx-action from v3
to v4.0.0, docker/metadata-action from v5 to v6.0.0, docker/build-push-action
from v6 to v7.0.0, actions/upload-artifact from v4 to v7.0.0, and
actions/download-artifact from v4 to v8.0.1; ensure each uses line items that
reference those action names (e.g., uses: docker/setup-qemu-action,
docker/setup-buildx-action, docker/metadata-action, docker/build-push-action,
actions/upload-artifact, actions/download-artifact) so the workflow picks up the
updated releases and their runtime improvements.

---

Nitpick comments:
In @.github/workflows/release_docker.yml:
- Around line 142-157: The imagetools create steps may run with no -t flags when
jq returns no matching tags; before invoking the docker buildx imagetools create
commands (the blocks that use jq -cr '.tags |
map(select(startswith("docker.io")) ...)' and the printf
"${DOCKERHUB_IMAGE}@sha256:%s " * for GHCR), add a guard that validates tag
lists from DOCKER_META_JSON (and the glob expansion) are non-empty and skip or
fail fast if empty; specifically, capture jq output into a variable (for
docker.io and ghcr.io), check its length, and only call docker buildx imagetools
create when that variable (and the corresponding printf/glob result) contains
entries to avoid invoking imagetools with no -t flags.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f137a96c-e310-48be-b51e-a33b7e811072

📥 Commits

Reviewing files that changed from the base of the PR and between df777f6 and 8b9aec2.

📒 Files selected for processing (2)
  • .github/workflows/release_docker.yml
  • Dockerfile

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