radixark/miles. This doc maps which Dockerfiles exist, the script that builds them, the PR-side build check, the rolling and versioned release workflows, and manual build and push behavior.
Dockerfiles
docker/Dockerfile — inputs & output
The Dockerfile is the build recipe: it provides the cu13 defaults and emits one image. build.py owns the variant → build-arg overrides (see Build script), including the cu12 base and wheels release.
Inputs (build-args)
Output — one
radixark/miles image for the platform buildx targets: the SGLang base, then the Python dependencies declared in requirements.txt, Megatron-LM at its branch default or caller-pinned commit, Miles, and the prebuilt wheels (sgl-router among them). A multi-arch build is one buildx run executed once per platform — TARGETARCH differs each time, so each arch installs its own wheels — and buildx pushes the two as a single manifest.
docker/Dockerfile.rocm is the ROCm counterpart (build-args GPU_ARCH + a ROCm SGLANG_IMAGE_TAG; the 7.2 variants also set APPLY_ROCR_VMMFIX=1, which downloads the ROCr VMM-pause fix .so from the WHEELS_TAG_ROCM release and installs it — ROCm 7.0 has no such regression and leaves it off).
Build script
docker/build.py builds and pushes the images. Select a build with --variant and a tag mode with --image-tag {dev,latest,custom}. The VARIANTS table is the source of truth for each variant’s image, target platforms, Dockerfile, and default build-args. Repeatable --build-arg KEY=VALUE options are appended after those defaults, so an explicit caller override wins.
The cu13 variants share one multi-arch CUDA base image and differ only in platforms.
cu13 runs a single buildx --platform linux/amd64,linux/arm64 — buildx builds both arches and pushes them as one manifest in a single shot, with the Dockerfile picking each layer’s wheels by TARGETARCH (see Dockerfile inputs), so docker pull auto-selects by host arch.
The Tag column is for --image-tag dev, which also pushes a timestamped dev-<YYYYMMDDHHMM> sibling; latest swaps the prefix to latest, custom uses --custom-tag. cu13 / cu13-x86 / cu13-aarch64 intentionally share radixark/miles:dev — the daily build runs cu13 (multi-arch), while a single-arch variant overwrites dev with one arch when run alone.
A multi-arch build (cu13) needs Buildx’s docker-container driver and is push-only — buildx writes the manifest straight to the registry, it can’t load into the local image store. Use cu13-x86 / cu13-aarch64 (single-platform; the arm64 one cross-builds via QEMU on an x86 host) for local single-arch iteration. Other flags: --push, --dry-run, --dockerfile, --custom-tag, and repeatable --build-arg KEY=VALUE.
PR build check (in pr-test.yml)
Dockerfile changes are build-tested on the PR itself, before merge — docker-build.yml only runs after a push to main, so without this breakage lands on main first.
pr-test.yml calls _build-pr-ci-image.yml after stage-a-cpu satisfies its success/bypass gate, while both CPU stages run without waiting for it. A PR keeps one image tag, radixark/miles:pr-<num>, for its whole life, and rebuilds it only when the content that feeds it changes:
So a rerun, or a push that touches only source files, reuses the image the PR already has instead of rebuilding an identical one. Non-docker PRs are untouched: no PR image, matrix on
dev, as before.
docker/image_inputs.py is the single source of truth for what counts as an input (docker/Dockerfile, docker/build.py, docker/install-kube-tools.sh, docker/verify_transformer_engine.py, docker/patch/**, requirements.txt). Dockerfile.rocm is deliberately excluded — it feeds pr-test-rocm.yml, not the cu13 image built here.
To rebuild when the inputs did not change — a moved base image, a floating dependency, a corrupt push — add the rebuild-ci-image label. Applying it starts a run that rebuilds and then removes the label, so it acts once rather than forcing a rebuild on every later run.
Rolling Docker build (docker-build.yml)
This workflow owns the rolling dev and latest image families. It has two jobs:
check-upstream(schedule /simulate_scheduleonly) — polls the inputs the image bakes: the HEAD SHA of sglangsglang-miles(sgl-project/sglang) and Megatron-LMmiles-main(radixark/Megatron-LM) — the source branches it builds — plus a fingerprint of the selectedyueming-yuan/miles-wheelsrolling release, so a rebuilt sgl-router or other wheel also triggers a build (re-uploads to the same tag are caught by fingerprint, not commit SHA). It compares against the values cached from the last build and setsshould_build=trueif any moved.milesitself is intentionally not polled — that would rebuild far too often. This is what stops the 12-hour cron from rebuilding an unchanged image, with one staleness bound: because the image also bakes amilescheckout,should_buildis forced totrueonce the last triggered build is 24h old, sodevnever drifts more than a day behind themilesrepo even when sglang / Megatron / wheels are quiet. (The cache file’s last line records the epoch of the last triggered build; it is only re-saved when a build fires.)build-and-push(self-hosteddocker-buildrunner) — callsdocker/build.pyto build + push, then conditionally pointslatestat the newdevand prunes old timestamped tags.
build-and-push runs when check-upstream was skipped, or ran and reported should_build=true.
Triggers: automatic vs manual
- Automatic (no human) — the schedule (cron 00:00 / 12:00 UTC, gated by
check-upstream) and any push tomainthat touchesdocker/Dockerfile,docker/install-kube-tools.sh,docker/verify_transformer_engine.py, orrequirements.txt. Both leave--variantempty and build two images:cu13→radixark/miles(multi-arch) andcu12-x86→radixark/miles:dev-cu12. - Manual —
workflow_dispatch(pick one variant — see Trigger a build yourself below) or runningdocker/build.pylocally. Only therocm7xx-mi3xximages have no automatic path (cu13-x86/cu13-aarch64just rebuild the samedevimage single-arch).
docker/build.py and docker/patch/** participate in PR image validation but are not main-push triggers; Release a Version treats them as manual preflight cases.
Tags & where it pushes
All images push to Docker Hub. CUDA variants →radixark/miles; ROCm variants → rocm/sgl-dev (a separate namespace). The tag a build writes depends only on --image-tag and the variant’s postfix:
What moves a shared tag:
--image-tag dev overwrites :dev (or :dev-cu12) and adds a timestamped sibling; on a scheduled run latest→dev and latest-cu12→dev-cu12 both advance; pruning likewise runs only on schedule, keeping the newest 20 of each series — dev-<ts> and dev-cu12-<ts> independently. Any workflow_dispatch — including simulate_schedule — writes its own tag(s) but never moves latest or prunes; only the real cron mutates published tags. See the trigger table above.
Trigger a build yourself
Manual builds run throughworkflow_dispatch — by default the image is built straight from the inputs you pass; with simulate_schedule, check-upstream runs first for signal but does not gate the manual build (see the workflow_dispatch rows in the table above for how this differs from schedule). Start one two ways:
- Web UI — Actions → “Docker Build & Push” → Run workflow, then fill the inputs below.
- CLI —
ghdispatches on the repo’s default branch; pass--ref <branch>to build another branch’s workflow.
Steps (build-and-push)
- checkout → Buildx → install Python + typer → Docker Hub login.
- Build + push via
build.py— automatic runs build bothcu13andcu12-x86; a manual dispatch builds only the one variant you picked. - schedule only — point
latest→devandlatest-cu12→dev-cu12. - schedule only — prune each timestamp series to the newest 20.
Push auth & permissions
Pushes use a Docker Hub credential, not your identity:- Remote (CI) — the workflow logs in with repo secrets
DOCKERHUB_USERNAME/DOCKERHUB_TOKEN, so you don’t hold the key — you just trigger the run, which needs repo Write access. No approval gate, butbuild-and-pushruns on aself-hostedrunner, so it only fires when one is online. - Local —
build.py --pushuses your owndocker login; you need push rights to the target namespace (radixark/miles, orrocm/sgl-devfor ROCm).
Versioned release build (release-docker.yml)
release-docker.yml checks out the supplied release ref, requires its committed release-lock.json, and passes the locked SGLang and Megatron-LM commits plus the checked-out Miles SHA as final --build-arg overrides. Rolling docker-build.yml keeps the branch defaults. Published tags and the guarded dispatch and retry procedure are documented in Release a Version.
Image retention (open)
docker-build.yml prunes dev-<timestamp> and dev-cu12-<timestamp> as separate series, keeping the newest 20 of each; dev / latest and dev-cu12 / latest-cu12 move forward. Ordinary PR, nightly, and weekly CI therefore has no durable image record. A release branch cut instead retags the newest timestamped dev image, or mutable dev when none exists, as prune-exempt release-vX.Y.Z-ci and records that tag in release-lock.json. The guarded preflight is documented in Release a Version.
