Skip to content

Docker Tag Monitor

Docker Tag Monitor #4

name: Docker Tag Monitor
on:
schedule:
- cron: '0 7 * * 6'
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run - check releases but do not create issues'
required: false
default: false
type: boolean
jobs:
check-releases:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v6.0.2
- name: Check for new Docker image releases
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
SEVEN_DAYS_AGO=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)
# Extract unique image:tag pairs from both config files
# Track highest version per repo and the tag suffixes actually in use
declare -A CURRENT_VERSIONS
declare -A REPO_SUFFIXES
# Only monitor these Docker Hub repos for updates
MONITORED_REPOS="lmsysorg/sglang lmsysorg/sglang-rocm vllm/vllm-openai vllm/vllm-openai-rocm"
# Normalize suffix: strip rc prefixes and date stamps so suffixes
# like "rc0-rocm720-mi35x-20260413" become "-rocm720-mi35x"
normalize_suffix() {
local s="$1"
s=$(echo "$s" | sed -E 's/^rc[0-9]+//; s/-[0-9]{8}(-[0-9]+)?$//')
[[ -z "$s" ]] && s="BARE"
echo "$s"
}
while IFS= read -r img; do
repo="${img%%:*}"
tag="${img#*:}"
# Only track repos we want to auto-update
[[ ! " $MONITORED_REPOS " == *" $repo "* ]] && continue
[[ "$img" == *nightly* || "$img" == *-dev* ]] && continue
base_ver=$(echo "$tag" | grep -oP 'v\d+\.\d+\.\d+(\.\w+)?' || true)
[[ -z "$base_ver" ]] && continue
suffix="${tag#"$base_ver"}"
suffix=$(normalize_suffix "$suffix")
prev="${CURRENT_VERSIONS[$repo]:-}"
if [[ -z "$prev" ]] || [[ "$(printf '%s\n%s\n' "$prev" "$base_ver" | sort -V | tail -1)" == "$base_ver" ]]; then
CURRENT_VERSIONS[$repo]="$base_ver"
fi
if [[ ! " ${REPO_SUFFIXES[$repo]:-} " == *" ${suffix} "* ]]; then
REPO_SUFFIXES[$repo]="${REPO_SUFFIXES[$repo]:-} ${suffix}"
fi
done < <(grep -h '^\s*image:' .github/configs/nvidia-master.yaml .github/configs/amd-master.yaml \
| sed 's/.*image:\s*//; s/"//g; s/'"'"'//g' | sort -u)
echo "=== Currently tracked repos ==="
for repo in "${!CURRENT_VERSIONS[@]}"; do
echo " $repo: ${CURRENT_VERSIONS[$repo]} (suffixes:${REPO_SUFFIXES[$repo]:-})"
done
FOUND_UPDATES=$(mktemp)
check_dockerhub() {
local repo="$1" current_ver="$2" allowed_suffixes="$3"
echo "Checking $repo (current: $current_ver, suffixes:$allowed_suffixes)..." >&2
local response
response=$(curl -sf "https://hub.docker.com/v2/repositories/${repo}/tags?page_size=50&ordering=last_updated" 2>/dev/null) || {
echo " Failed to fetch tags for $repo" >&2
return
}
echo "$response" | jq -r --arg since "$SEVEN_DAYS_AGO" '
(.results // [])[]
| select(.last_updated > $since)
| select(.name | test("^v[0-9]"))
| "\(.name)\t\(.last_updated)"
' 2>/dev/null | while IFS=$'\t' read -r tag updated; do
local tag_ver
tag_ver=$(echo "$tag" | grep -oP 'v\d+\.\d+\.\d+(\.\w+)?' || true)
[[ -z "$tag_ver" ]] && continue
[[ "$tag" == *nightly* || "$tag" == *dev* ]] && continue
# Only report tags whose normalized suffix matches one already in use
local tag_suffix
tag_suffix=$(normalize_suffix "${tag#"$tag_ver"}")
local matched=false
for s in $allowed_suffixes; do
if [[ "$tag_suffix" == "$s" ]]; then
matched=true
break
fi
done
[[ "$matched" == false ]] && continue
local higher
higher=$(printf '%s\n%s\n' "$current_ver" "$tag_ver" | sort -V | tail -1)
if [[ "$higher" == "$tag_ver" && "$higher" != "$current_ver" ]]; then
echo " Found update: $tag ($tag_ver > $current_ver)" >&2
echo "${repo}|${tag}|${tag_ver}|${current_ver}|${updated}"
fi
done || true
}
for repo in "${!CURRENT_VERSIONS[@]}"; do
current="${CURRENT_VERSIONS[$repo]}"
suffixes="${REPO_SUFFIXES[$repo]:-}"
check_dockerhub "$repo" "$current" "$suffixes" >> "$FOUND_UPDATES" || true
done
UPDATES=$(grep '|' "$FOUND_UPDATES" | sort -u || true)
rm -f "$FOUND_UPDATES"
if [[ -z "$UPDATES" ]]; then
echo "No new releases found in the last 7 days."
echo "has_updates=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "has_updates=true" >> "$GITHUB_OUTPUT"
# Build issue body and write to file
BODY_FILE=$(mktemp)
{
echo "## Docker Image Updates Available"
echo ""
echo "New release tags have been detected on Docker Hub within the last 7 days."
echo ""
echo "### Updates Found"
echo ""
echo "| Image | New Tag | New Version | Current Version | Published |"
echo "|-------|---------|-------------|-----------------|-----------|"
while IFS='|' read -r repo tag tag_ver cur_ver updated; do
pub_date=$(echo "$updated" | cut -dT -f1)
echo "| \`$repo\` | \`$tag\` | $tag_ver | $cur_ver | $pub_date |"
done <<< "$UPDATES"
echo ""
echo "---"
echo ""
echo "@claude Please update the configurations:"
echo ""
echo "1. Update image tags in \`.github/configs/nvidia-master.yaml\` and/or \`.github/configs/amd-master.yaml\`"
echo "2. Add entries to \`perf-changelog.yaml\` documenting the version changes"
echo "3. Create separate PRs grouped by framework and image family, and link each PR."
echo ""
echo "Group by framework plus CUDA/ROCm image family (for example sglang-cuda, sglang-rocm, vllm-cuda, vllm-rocm, atom, trt), not by individual GPU. Split by GPU only when an update is genuinely hardware-specific."
echo ""
echo "Focus on updating single-node configurations. For each framework/image family, check if there are multiple CUDA/ROCm versions available and choose appropriately based on current usage patterns in the configs."
} > "$BODY_FILE"
echo "body_file=$BODY_FILE" >> "$GITHUB_OUTPUT"
echo "=== Issue body ==="
cat "$BODY_FILE"
- name: Create issue for updates
if: steps.check.outputs.has_updates == 'true' && inputs.dry_run != true
env:
GH_TOKEN: ${{ secrets.CLAUDE_PAT }}
run: |
DATE=$(date +%Y-%m-%d)
BODY_FILE="${{ steps.check.outputs.body_file }}"
EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --label "docker-update" --state open --json number --jq '.[0].number // empty')
if [[ -n "$EXISTING" ]]; then
echo "Updating existing issue #$EXISTING"
jq -Rs '{body: .}' "$BODY_FILE" \
| gh api --method POST "repos/$GITHUB_REPOSITORY/issues/$EXISTING/comments" --input -
else
jq -n \
--arg title "[Auto] Docker Image Updates Available - $DATE" \
--rawfile body "$BODY_FILE" \
'{title: $title, body: $body, labels: ["docker-update", "claude-task"]}' \
| gh api --method POST "repos/$GITHUB_REPOSITORY/issues" --input -
fi
- name: Dry run output
if: steps.check.outputs.has_updates == 'true' && inputs.dry_run == true
run: |
echo "## Dry Run - Updates Found"
cat "${{ steps.check.outputs.body_file }}"