Skip to content

Commit d50d2c3

Browse files
ci: drop floating tag, skip when SHA already exists in ECR
ECR repos are IMMUTABLE — pushing `deploy` (or any floating tag) a second time fails after the first build. Re-runs of the same commit fail for the same reason on the SHA tag. Fix: only push SHA + (on tag pushes) semver. Pre-check via `aws ecr describe-images` before building — if the image is already there, skip the entire matrix entry as a no-op success. Mirrored in scripts/build-and-push.sh so local builds behave identically. The IAM user used by CI needs `ecr:DescribeImages` added to its policy for the pre-check; the existing push perms are unchanged.
1 parent c247b8a commit d50d2c3

2 files changed

Lines changed: 56 additions & 18 deletions

File tree

.github/workflows/build-images.yml

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,21 @@
1515
# VITE_AGENTOS_DEFAULT_MODEL — baked into the SPA bundle at build time
1616
#
1717
# Triggers:
18-
# - push to deploy → build + push (tags: <sha>, deploy)
19-
# - tag v* → build + push (tags: <sha>, deploy, <tag>)
18+
# - push to deploy → build + push (tag: <sha>)
19+
# - tag v* → build + push (tags: <sha>, <semver>)
2020
# - workflow_dispatch → manual
2121
# - pull_request to deploy → build only (no push)
2222
#
2323
# `deploy` is the long-lived release branch — merge feature branches into it
2424
# when you're ready to ship to EKS. Main can move independently.
25+
#
26+
# ── Tag policy ──────────────────────────────────────────────────────────────
27+
# ECR repos are IMMUTABLE, so we only ever push SHA-based tags + semver tags.
28+
# Floating `deploy` / `main` tags would conflict with IMMUTABLE on every
29+
# subsequent push. To roll forward: bump kustomize image tag to the new SHA.
30+
#
31+
# If the SHA already exists in ECR (re-run of the same commit), the workflow
32+
# detects it and skips the build entirely → no-op success, no error.
2533
name: build-images
2634

2735
on:
@@ -95,17 +103,34 @@ jobs:
95103
run: |
96104
REGISTRY="${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com"
97105
REPO="agentos/${{ matrix.name }}"
106+
# ECR is IMMUTABLE → SHA-only by default, plus semver on tag pushes.
98107
TAGS="${REGISTRY}/${REPO}:${{ steps.tag.outputs.sha }}"
99-
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/deploy" ]]; then
100-
TAGS="${TAGS},${REGISTRY}/${REPO}:deploy"
101-
fi
102108
if [[ -n "${{ steps.tag.outputs.semver }}" ]]; then
103109
TAGS="${TAGS},${REGISTRY}/${REPO}:${{ steps.tag.outputs.semver }}"
104110
fi
105111
echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"
112+
echo "registry=${REGISTRY}" >> "$GITHUB_OUTPUT"
113+
echo "repo=${REPO}" >> "$GITHUB_OUTPUT"
114+
115+
- name: Check if image already exists at this SHA
116+
id: exists
117+
if: github.event_name != 'pull_request'
118+
run: |
119+
# ECR is IMMUTABLE — if the SHA tag exists, ANY push (even the
120+
# identical bytes) is rejected. Make re-runs idempotent by exiting
121+
# the build cleanly when the image is already there.
122+
if aws ecr describe-images \
123+
--repository-name "${{ steps.tags.outputs.repo }}" \
124+
--image-ids "imageTag=${{ steps.tag.outputs.sha }}" \
125+
--region "${{ env.AWS_REGION }}" >/dev/null 2>&1; then
126+
echo "skip=true" >> "$GITHUB_OUTPUT"
127+
echo "::notice title=Skipping build::Image ${{ steps.tags.outputs.repo }}:${{ steps.tag.outputs.sha }} already exists in ECR — no rebuild needed."
128+
else
129+
echo "skip=false" >> "$GITHUB_OUTPUT"
130+
fi
106131
107132
- name: Build and push ${{ matrix.name }} (agentos-server)
108-
if: matrix.build_args == ''
133+
if: matrix.build_args == '' && steps.exists.outputs.skip != 'true'
109134
uses: docker/build-push-action@v5
110135
with:
111136
context: .
@@ -116,7 +141,7 @@ jobs:
116141
cache-to: type=gha,mode=max,scope=${{ matrix.name }}
117142

118143
- name: Build and push ${{ matrix.name }} (harness variant)
119-
if: matrix.build_args == 'harness'
144+
if: matrix.build_args == 'harness' && steps.exists.outputs.skip != 'true'
120145
uses: docker/build-push-action@v5
121146
with:
122147
context: .
@@ -129,7 +154,7 @@ jobs:
129154
cache-to: type=gha,mode=max,scope=${{ matrix.name }}
130155

131156
- name: Build and push agentos-spa (with VITE_ build args)
132-
if: matrix.build_args == 'spa'
157+
if: matrix.build_args == 'spa' && steps.exists.outputs.skip != 'true'
133158
uses: docker/build-push-action@v5
134159
with:
135160
context: .
@@ -146,15 +171,20 @@ jobs:
146171
- name: Summary
147172
if: github.event_name != 'pull_request'
148173
run: |
149-
echo "### Pushed ${{ matrix.name }}" >> "$GITHUB_STEP_SUMMARY"
150-
echo "" >> "$GITHUB_STEP_SUMMARY"
151-
echo "Tags:" >> "$GITHUB_STEP_SUMMARY"
152-
for t in $(echo "${{ steps.tags.outputs.tags }}" | tr ',' '\n'); do
153-
echo "- \`$t\`" >> "$GITHUB_STEP_SUMMARY"
154-
done
174+
if [[ "${{ steps.exists.outputs.skip }}" == "true" ]]; then
175+
echo "### ${{ matrix.name }} — skipped" >> "$GITHUB_STEP_SUMMARY"
176+
echo "" >> "$GITHUB_STEP_SUMMARY"
177+
echo "Image already exists at \`${{ steps.tags.outputs.repo }}:${{ steps.tag.outputs.sha }}\` — no rebuild." >> "$GITHUB_STEP_SUMMARY"
178+
else
179+
echo "### Pushed ${{ matrix.name }}" >> "$GITHUB_STEP_SUMMARY"
180+
echo "" >> "$GITHUB_STEP_SUMMARY"
181+
echo "Tags:" >> "$GITHUB_STEP_SUMMARY"
182+
for t in $(echo "${{ steps.tags.outputs.tags }}" | tr ',' '\n'); do
183+
echo "- \`$t\`" >> "$GITHUB_STEP_SUMMARY"
184+
done
185+
fi
155186
echo "" >> "$GITHUB_STEP_SUMMARY"
156-
REGISTRY="${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com"
157187
echo "Bump kustomize:" >> "$GITHUB_STEP_SUMMARY"
158188
echo '```bash' >> "$GITHUB_STEP_SUMMARY"
159-
echo "kustomize edit set image agentos/${{ matrix.name }}=${REGISTRY}/agentos/${{ matrix.name }}:${{ steps.tag.outputs.sha }}" >> "$GITHUB_STEP_SUMMARY"
189+
echo "kustomize edit set image agentos/${{ matrix.name }}=${{ steps.tags.outputs.registry }}/agentos/${{ matrix.name }}:${{ steps.tag.outputs.sha }}" >> "$GITHUB_STEP_SUMMARY"
160190
echo '```' >> "$GITHUB_STEP_SUMMARY"

scripts/build-and-push.sh

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ build_one() {
4545
local repo="agentos/${name}"
4646
local image="${REGISTRY}/${repo}:${IMAGE_TAG}"
4747

48+
# ECR repos are IMMUTABLE. If this SHA already exists, exit cleanly so
49+
# repeated runs of the same commit are no-ops (matches CI behavior).
50+
if [ "${PUSH}" = "1" ] && aws ecr describe-images \
51+
--repository-name "${repo}" \
52+
--image-ids "imageTag=${IMAGE_TAG}" \
53+
--region "${AWS_REGION}" >/dev/null 2>&1; then
54+
echo "─── skipping ${name} ─── image ${image} already in ECR"
55+
return 0
56+
fi
57+
4858
echo
4959
echo "─── building ${name} ───"
5060
echo " dockerfile : ${dockerfile}"
@@ -54,13 +64,11 @@ build_one() {
5464
docker build \
5565
-f "${dockerfile}" \
5666
-t "${image}" \
57-
-t "${REGISTRY}/${repo}:main" \
5867
"${extra_args[@]}" \
5968
"${context}"
6069

6170
if [ "${PUSH}" = "1" ]; then
6271
docker push "${image}"
63-
docker push "${REGISTRY}/${repo}:main"
6472
echo "✓ pushed ${image}"
6573
else
6674
echo "✓ built ${image} (PUSH=0, skipping push)"

0 commit comments

Comments
 (0)