Skip to content

Commit a01408d

Browse files
committed
feat: add GHCR cleanup job for old container images
- Add cleanup-old-images job that runs after build and security scan - Enable cleanup on scheduled runs (weekly vulnerability scans) - Delete all untagged image versions (keep-n-untagged: 0) - Keep 10 most recent tagged versions for rollback - Protect production tags: base-ubuntu, node, python - Clean up ghost/partial multi-arch and orphaned images - Only runs on main branch with continue-on-error for safety
1 parent c6287d9 commit a01408d

File tree

4 files changed

+95
-9
lines changed

4 files changed

+95
-9
lines changed

β€Ž.github/workflows/build-and-publish.ymlβ€Ž

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ jobs:
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
@@ -73,6 +74,26 @@ jobs:
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"

β€ŽREADME.mdβ€Ž

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99

1010
This repository implements automated security scanning for all container images:
1111

12-
- πŸ” **Vulnerability Scanning**: All images are scanned with [Trivy](https://trivy.dev/) for CVEs
13-
- πŸ“‹ **SBOM Generation**: Software Bill of Materials generated in CycloneDX format
14-
- πŸ”„ **Scheduled Scans**: Weekly security scans catch newly discovered vulnerabilities
15-
- πŸ“Š **Security Dashboard**: View results in the [Security tab](https://github.com/jmcombs/devcontainer/security/code-scanning)
12+
- **Vulnerability Scanning**: All images are scanned with [Trivy](https://trivy.dev/) for CVEs
13+
- **SBOM Generation**: Software Bill of Materials generated in CycloneDX format
14+
- **Scheduled Scans**: Weekly security scans catch newly discovered vulnerabilities
15+
- **Security Dashboard**: View results in the [Security tab](https://github.com/jmcombs/devcontainer/security/code-scanning)
1616

1717
A collection of [Development Container](https://containers.dev/) definitions for creating consistent, reproducible development environments. This repository provides pre-configured `devcontainer` images to streamline setting up development environments in tools like Visual Studio Code, GitHub Codespaces, or other container-based IDEs.
1818

β€Žsrc/node/.devcontainer/devcontainer.jsonβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"image": "docker.io/jmcombs/devcontainer:base-ubuntu",
2+
"image": "ghcr.io/jmcombs/devcontainer:base-ubuntu",
33
"features": {
44
"ghcr.io/devcontainers/features/node:1": {},
55
"ghcr.io/jmcombs/devcontainer-features/ngrok:1": {}

β€Žsrc/python/.devcontainer/devcontainer.jsonβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"image": "docker.io/jmcombs/devcontainer:base-ubuntu",
2+
"image": "ghcr.io/jmcombs/devcontainer:base-ubuntu",
33
"features": {
44
"ghcr.io/devcontainers/features/python:1": {},
55
"ghcr.io/jmcombs/devcontainer-features/ngrok:1": {}

0 commit comments

Comments
Β (0)