6464 - name : Install Devcontainer CLI
6565 run : npm install -g @devcontainers/cli
6666
67+ # Step 1: Build and publish base-ubuntu container first
6768 - name : Build and publish base-ubuntu container
6869 run : |
6970 cd src/base-ubuntu
7374 --output type=registry \
7475 ${{ env.LABEL_ARGS }}
7576
77+ # Step 2: Extract the digest of the freshly built base-ubuntu image
78+ - name : Get base-ubuntu image digest
79+ id : base-digest
80+ run : |
81+ # Get the digest of the multi-platform manifest from the registry
82+ DIGEST=$(docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:base-ubuntu --format '{{json .Manifest.Digest}}' | tr -d '"')
83+ echo "digest=$DIGEST" >> $GITHUB_OUTPUT
84+ echo "full_ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}" >> $GITHUB_OUTPUT
85+ echo "::notice::Base image digest: $DIGEST"
86+
87+ # Step 3: Update node devcontainer.json to use the exact base-ubuntu digest
88+ - name : Update node devcontainer.json with base image digest
89+ run : |
90+ BASE_IMAGE="${{ steps.base-digest.outputs.full_ref }}"
91+ echo "Updating node base image to: $BASE_IMAGE"
92+ jq --arg img "$BASE_IMAGE" '.image = $img' src/node/.devcontainer/devcontainer.json > tmp.json
93+ mv tmp.json src/node/.devcontainer/devcontainer.json
94+ cat src/node/.devcontainer/devcontainer.json
95+
96+ # Step 4: Build and publish node container using the pinned base image
7697 - name : Build and publish node container
7798 run : |
7899 cd src/node
@@ -82,6 +103,16 @@ jobs:
82103 --output type=registry \
83104 ${{ env.LABEL_ARGS }}
84105
106+ # Step 5: Update python devcontainer.json to use the exact base-ubuntu digest
107+ - name : Update python devcontainer.json with base image digest
108+ run : |
109+ BASE_IMAGE="${{ steps.base-digest.outputs.full_ref }}"
110+ echo "Updating python base image to: $BASE_IMAGE"
111+ jq --arg img "$BASE_IMAGE" '.image = $img' src/python/.devcontainer/devcontainer.json > tmp.json
112+ mv tmp.json src/python/.devcontainer/devcontainer.json
113+ cat src/python/.devcontainer/devcontainer.json
114+
115+ # Step 6: Build and publish python container using the pinned base image
85116 - name : Build and publish python container
86117 run : |
87118 cd src/python
@@ -118,7 +149,7 @@ jobs:
118149 run : docker pull ghcr.io/${{ github.repository }}:${{ matrix.image }}
119150
120151 - name : Generate SBOM with Trivy
121- uses : aquasecurity/trivy-action@master
152+ uses : aquasecurity/trivy-action@0.31.0
122153 with :
123154 image-ref : " ghcr.io/${{ github.repository }}:${{ matrix.image }}"
124155 format : " cyclonedx"
@@ -132,15 +163,70 @@ jobs:
132163 retention-days : 90
133164
134165 - name : Scan for vulnerabilities with Trivy
135- uses : aquasecurity/trivy-action@master
166+ uses : aquasecurity/trivy-action@0.31.0
136167 with :
137168 image-ref : " ghcr.io/${{ github.repository }}:${{ matrix.image }}"
138169 format : " sarif"
139170 output : " trivy-${{ matrix.image }}.sarif"
140171 severity : " CRITICAL,HIGH,MEDIUM"
141172
142173 - name : Upload Trivy scan results to GitHub Security tab
143- uses : github/codeql-action/upload-sarif@v3
174+ uses : github/codeql-action/upload-sarif@v4
144175 with :
145176 sarif_file : " trivy-${{ matrix.image }}.sarif"
146177 category : " container-${{ matrix.image }}"
178+
179+ # Cleanup old container images from GHCR
180+ cleanup-old-images :
181+ needs : [build-and-publish, security-scan]
182+ runs-on : ubuntu-latest
183+ # Only run on main branch to prevent accidental deletions from PR builds
184+ # Runs on push, schedule (weekly scans), and manual dispatch
185+ if : github.ref == 'refs/heads/main'
186+ permissions :
187+ packages : write
188+ # Don't fail the workflow if cleanup fails
189+ continue-on-error : true
190+
191+ steps :
192+ - name : Log cleanup start
193+ run : |
194+ echo "=== GHCR Cleanup Job ==="
195+ echo "Package: ghcr.io/${{ github.repository }}"
196+ echo "Trigger: ${{ github.event_name }}"
197+ echo ""
198+ echo "Retention Policy:"
199+ echo " - Keep current tags: base-ubuntu, node, python"
200+ echo " - Keep 10 most recent tagged versions"
201+ echo " - Delete ALL untagged versions"
202+ echo " - Delete ghost/partial multi-arch images"
203+ echo " - Delete orphaned referrer images"
204+ echo ""
205+
206+ # Single cleanup step for the devcontainer package
207+ # All image variants (base-ubuntu, node, python) are tags within this single package
208+ - name : Cleanup old container image versions
209+ uses : dataaxiom/ghcr-cleanup-action@v1
210+ with :
211+ token : ${{ secrets.GITHUB_TOKEN }}
212+ package : devcontainer
213+ owner : ${{ github.repository_owner }}
214+ # Delete all untagged images - we use specific tags for production
215+ delete-untagged : true
216+ keep-n-untagged : 0
217+ # Keep recent tagged versions for rollback capability
218+ keep-n-tagged : 10
219+ # Protect the current production tags from deletion
220+ exclude-tags : base-ubuntu,node,python
221+ # Clean up corrupted multi-architecture images
222+ delete-ghost-images : true
223+ delete-partial-images : true
224+ # Clean up orphaned attestation/referrer images
225+ delete-orphaned-images : true
226+ # Set to true for testing, false for actual cleanup
227+ dry-run : false
228+
229+ - name : Log cleanup complete
230+ run : |
231+ echo "=== Cleanup Complete ==="
232+ echo "View packages: https://github.com/${{ github.repository_owner }}?tab=packages&repo_name=devcontainer"
0 commit comments