fix(docker): enable build cache with per-platform registry cache#23
fix(docker): enable build cache with per-platform registry cache#23
Conversation
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.
| 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>" |
There was a problem hiding this comment.
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>"
WalkthroughThe 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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/REVISIONand OCILABELs to the finalruntimestage 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.
| # 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 " *) |
There was a problem hiding this comment.
위 Docker Hub manifest 생성과 동일하게, GHCR 쪽 jq 필터에도 \" 이 포함되어 있어 쉘/ jq 파싱 오류를 유발할 수 있습니다. single-quote 안에서는 double quote를 이스케이프할 필요가 없으므로 backslash를 제거해 jq 필터가 정상 파싱되도록 수정해주세요.
| 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'] }} |
There was a problem hiding this comment.
outputs: 값에 \"(역슬래시+따옴표)가 들어가 있는데, YAML plain scalar에서는 이스케이프가 처리되지 않아 역슬래시가 그대로 buildx로 전달됩니다. 이 경우 --output type=image,\"name=...\" 형태가 되어 output 파싱/이미지 이름 파싱이 실패할 수 있습니다. 역슬래시를 제거하고(필요하면 YAML 값 전체를 single/double quote로 감싸서) buildx가 기대하는 type=image,"name=..."... / type=image,name=... 형태로 전달되도록 수정해주세요.
| # 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 " *) |
There was a problem hiding this comment.
이 jq -cr '...' 필터 문자열은 single quote로 감싸여 있으므로 내부의 double quote(")를 백슬래시로 이스케이프할 필요가 없습니다. 현재처럼 \"가 들어가면 쉘이 이를 처리하지 않고 jq에 그대로 전달되어 필터 파싱이 실패할 수 있습니다. startswith(\"docker.io\") / \"-t \" 처럼 백슬래시가 붙은 부분에서 백슬래시를 제거해 주세요.
There was a problem hiding this comment.
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 | 🟠 MajorUpdate GitHub Actions to current stable versions.
Several actions are pinned to outdated versions:
docker/setup-qemu-action@v3→ v4.0.0docker/setup-buildx-action@v3→ v4.0.0docker/metadata-action@v5→ v6.0.0docker/build-push-action@v6→ v7.0.0actions/upload-artifact@v4→ v7.0.0actions/download-artifact@v4→ v8.0.1Current 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
jqfiltering andprintfglob expansion correctly build theimagetools createcommands. However, if no tags match the filter (e.g.,startswith("docker.io")returns empty), the command would run without any-tflags, which may cause unexpected behavior.Given that
type=edgealways generates tags for pushes tomain, 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
📒 Files selected for processing (2)
.github/workflows/release_docker.ymlDockerfile
Summary
ARG VERSION/REVISION이python-base스테이지에 선언되어 매 커밋마다 모든 하위 레이어(uv sync, playwright install)가 캐시 무효화됨ARG/LABEL을 최종runtime스테이지로 이동하여 중간 스테이지 캐시 보존Test plan
Summary by CodeRabbit