Skip to content

Docker Build & Push #118

Docker Build & Push

Docker Build & Push #118

Workflow file for this run

name: Docker Build & Push
on:
push:
branches:
- main
paths:
- 'docker/Dockerfile'
schedule:
# Every 12 hours: midnight and noon UTC
- cron: '0 0,12 * * *'
workflow_dispatch:
inputs:
variant:
description: 'Build variant'
required: true
type: choice
options:
- primary
- cu129-arm64
- cu13-arm64
- debug
image_tag:
description: 'Tag mode: latest, dev (rolling + timestamped), or custom'
required: true
type: choice
options:
- dev
- latest
- custom
custom_tag:
description: 'Custom tag name (only used when image_tag is custom)'
required: false
default: ''
type: string
dockerfile:
description: 'Path to Dockerfile (e.g. docker/Dockerfile)'
required: false
default: 'docker/Dockerfile'
type: string
simulate_schedule:
description: '[DEBUG] Simulate a scheduled build (runs upstream check)'
required: false
type: boolean
default: false
jobs:
check-upstream:
# Only run on schedule (or simulated schedule) — check if upstream repos have new commits
if: github.event_name == 'schedule' || inputs.simulate_schedule == true
runs-on: ubuntu-latest
outputs:
should_build: ${{ steps.check.outputs.should_build }}
steps:
- name: Restore last known upstream SHAs
uses: actions/cache/restore@v4
with:
path: last_upstream_shas.txt
key: upstream-shas-
restore-keys: upstream-shas-
- name: Check for new upstream commits
id: check
run: |
SHOULD_BUILD=false
# Check sglang (sglang-miles branch)
SGLANG_SHA=$(curl -s -H "Accept: application/vnd.github.sha" \
"https://api.github.com/repos/sgl-project/sglang/commits/sglang-miles")
# Check Megatron-LM (main branch)
MEGATRON_SHA=$(curl -s -H "Accept: application/vnd.github.sha" \
"https://api.github.com/repos/NVIDIA/Megatron-LM/commits/main")
echo "sglang HEAD: ${SGLANG_SHA}"
echo "megatron HEAD: ${MEGATRON_SHA}"
# Compare against last known SHAs stored in cache
CACHE_FILE="last_upstream_shas.txt"
if [ -f "$CACHE_FILE" ]; then
LAST_SGLANG=$(sed -n '1p' "$CACHE_FILE")
LAST_MEGATRON=$(sed -n '2p' "$CACHE_FILE")
else
LAST_SGLANG=""
LAST_MEGATRON=""
fi
if [ "$SGLANG_SHA" != "$LAST_SGLANG" ] || [ "$MEGATRON_SHA" != "$LAST_MEGATRON" ]; then
SHOULD_BUILD=true
echo "Upstream change detected"
else
echo "No upstream changes"
fi
# Save current SHAs for next run
echo "${SGLANG_SHA}" > "$CACHE_FILE"
echo "${MEGATRON_SHA}" >> "$CACHE_FILE"
echo "should_build=${SHOULD_BUILD}" >> "$GITHUB_OUTPUT"
- name: Save upstream SHAs to cache
if: steps.check.outputs.should_build == 'true'
uses: actions/cache/save@v4
with:
path: last_upstream_shas.txt
key: upstream-shas-${{ github.run_id }}
build-and-push:
needs: [check-upstream]
# schedule: only build if upstream changed
# push/dispatch: check-upstream skipped → build
if: |
always() &&
(needs.check-upstream.result == 'skipped' || needs.check-upstream.outputs.should_build == 'true')
runs-on: self-hosted
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=moby/buildkit:latest
network=host
- name: Install Python + dependencies
run: |
apt-get update && apt-get install -y python3 python3-pip
pip3 install --break-system-packages typer
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
run: |
python3 docker/build.py \
--variant ${{ inputs.variant || 'primary' }} \
--image-tag ${{ inputs.image_tag || 'dev' }} \
--dockerfile ${{ inputs.dockerfile || 'docker/Dockerfile' }} \
${{ inputs.custom_tag && format('--custom-tag {0}', inputs.custom_tag) || '' }} \
--push
- name: Point latest to current dev
if: github.event_name == 'schedule' || inputs.simulate_schedule == true
run: |
docker buildx imagetools create -t radixark/miles:latest radixark/miles:dev
- name: Prune old dev tags
if: github.event_name == 'schedule'
run: |
KEEP=20
IMAGE=radixark/miles
# Authenticate with Docker Hub API
TOKEN=$(curl -s -X POST "https://hub.docker.com/v2/users/login/" \
-H "Content-Type: application/json" \
-d '{"username":"${{ secrets.DOCKERHUB_USERNAME }}","password":"${{ secrets.DOCKERHUB_TOKEN }}"}' \
| jq -r .token)
# Collect all dev-YYYYMMDDHHMM tags (paginated)
DEV_TAGS=""
URL="https://hub.docker.com/v2/repositories/${IMAGE}/tags/?page_size=100"
while [ "$URL" != "null" ] && [ -n "$URL" ]; do
RESP=$(curl -s -H "Authorization: JWT ${TOKEN}" "$URL")
PAGE_TAGS=$(echo "$RESP" | jq -r '.results[].name' | grep -E '^dev-[0-9]{12}$' || true)
DEV_TAGS="${DEV_TAGS}${PAGE_TAGS}"$'\n'
URL=$(echo "$RESP" | jq -r '.next')
done
# Sort and find tags to delete
SORTED=$(echo "$DEV_TAGS" | grep -v '^$' | sort)
COUNT=$(echo "$SORTED" | wc -l | tr -d ' ')
echo "Found ${COUNT} dev tags"
if [ "$COUNT" -le "$KEEP" ]; then
echo "Nothing to prune (keep=${KEEP})"
exit 0
fi
DELETE_COUNT=$((COUNT - KEEP))
TO_DELETE=$(echo "$SORTED" | head -n "$DELETE_COUNT")
echo "Pruning ${DELETE_COUNT} old dev tags..."
echo "$TO_DELETE" | while read -r TAG; do
[ -z "$TAG" ] && continue
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: JWT ${TOKEN}" \
"https://hub.docker.com/v2/repositories/${IMAGE}/tags/${TAG}/")
if [ "$HTTP_CODE" = "204" ]; then
echo " Deleted ${TAG}"
else
echo " Failed to delete ${TAG} (HTTP ${HTTP_CODE})"
fi
done
build-and-push-dev-glm:
needs: [build-and-push]
# Only rebuild dev-glm when the dev image was built (schedule, push to main, or dispatch with image_tag=dev)
if: needs.build-and-push.result == 'success' && (github.event_name == 'schedule' || inputs.simulate_schedule == true)
runs-on: self-hosted
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=moby/buildkit:latest
network=host
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push dev-glm
run: |
docker buildx build \
-f docker/glm5/Dockerfile.dev-glm \
-t radixark/miles:dev-glm \
--push \
.