|
| 1 | +name: Build a Docker image on remote BuildKit |
| 2 | + |
| 3 | +description: > |
| 4 | + Build (and push) a Docker image from an OSDC/ARC runner, which has no host docker daemon. |
| 5 | + Registers the in-cluster remote BuildKit builder for the runner's architecture and retries |
| 6 | + connection-phase failures while the autoscaled builder pool is cold, so callers get a build |
| 7 | + that either succeeds or fails for a real reason. The step blocks until the image is pushed, |
| 8 | + so downstream jobs can depend on it with `needs:`. |
| 9 | +
|
| 10 | +inputs: |
| 11 | + command: |
| 12 | + description: > |
| 13 | + A build command to run on the remote builder instead of assembling a `docker buildx build` |
| 14 | + invocation — a repo build script or make target, for example. When set, the buildx inputs |
| 15 | + below (tags, context, file, build-args, labels, target, push, cache-from, cache-to) are |
| 16 | + ignored and the `image` output is not set. The command is responsible for its own tags and |
| 17 | + for `--push`; note there is no local daemon, so `--load` cannot work. |
| 18 | + default: '' |
| 19 | + tags: |
| 20 | + description: > |
| 21 | + Newline- or comma-separated list of image tags, e.g. ghcr.io/pytorch/my-image:abc123. |
| 22 | + Required unless `command` is set. |
| 23 | + default: '' |
| 24 | + context: |
| 25 | + description: Build context path. |
| 26 | + default: . |
| 27 | + file: |
| 28 | + description: Path to the Dockerfile. Defaults to <context>/Dockerfile. |
| 29 | + default: '' |
| 30 | + build-args: |
| 31 | + description: Newline-separated KEY=VALUE build arguments. |
| 32 | + default: '' |
| 33 | + labels: |
| 34 | + description: Newline-separated KEY=VALUE image labels, e.g. the output of docker/metadata-action. |
| 35 | + default: '' |
| 36 | + target: |
| 37 | + description: Dockerfile stage to build. |
| 38 | + default: '' |
| 39 | + push: |
| 40 | + description: > |
| 41 | + Push the image to the registry. There is no local docker daemon on an OSDC runner, so |
| 42 | + `--load` has nothing to load into; a build with push=false is only useful as a syntax check. |
| 43 | + default: 'true' |
| 44 | + cache-from: |
| 45 | + description: Newline-separated buildx --cache-from entries. |
| 46 | + default: '' |
| 47 | + cache-to: |
| 48 | + description: Newline-separated buildx --cache-to entries. |
| 49 | + default: '' |
| 50 | + connect-attempts: |
| 51 | + description: > |
| 52 | + How many times to retry a connection-phase failure before giving up. The default of 480 at |
| 53 | + the default 15s delay waits roughly two hours for a builder, which covers a fully cold or |
| 54 | + saturated pool (one build per builder pod, so a burst queues). Bound the real ceiling with |
| 55 | + the job's `timeout-minutes`, not by lowering this. |
| 56 | + default: '480' |
| 57 | + connect-delay: |
| 58 | + description: Seconds to wait between connection-phase retries. |
| 59 | + default: '15' |
| 60 | + |
| 61 | +outputs: |
| 62 | + image: |
| 63 | + description: > |
| 64 | + The first tag passed in, for convenience when wiring up downstream jobs. Empty in command |
| 65 | + mode. |
| 66 | + value: ${{ steps.build.outputs.image }} |
| 67 | + |
| 68 | +runs: |
| 69 | + using: composite |
| 70 | + steps: |
| 71 | + - name: Build and push with remote BuildKit |
| 72 | + id: build |
| 73 | + shell: bash |
| 74 | + env: |
| 75 | + COMMAND: ${{ inputs.command }} |
| 76 | + TAGS: ${{ inputs.tags }} |
| 77 | + CONTEXT: ${{ inputs.context }} |
| 78 | + FILE: ${{ inputs.file }} |
| 79 | + BUILD_ARGS: ${{ inputs.build-args }} |
| 80 | + LABELS: ${{ inputs.labels }} |
| 81 | + TARGET: ${{ inputs.target }} |
| 82 | + PUSH: ${{ inputs.push }} |
| 83 | + CACHE_FROM: ${{ inputs.cache-from }} |
| 84 | + CACHE_TO: ${{ inputs.cache-to }} |
| 85 | + REMOTE_BUILDKIT_CONNECT_ATTEMPTS: ${{ inputs.connect-attempts }} |
| 86 | + REMOTE_BUILDKIT_CONNECT_DELAY: ${{ inputs.connect-delay }} |
| 87 | + run: | |
| 88 | + set -euo pipefail |
| 89 | +
|
| 90 | + # Command mode: hand the whole thing to the helper, which registers the |
| 91 | + # builder and retries only connection-phase failures. |
| 92 | + if [[ -n "${COMMAND}" ]]; then |
| 93 | + exec bash "${GITHUB_ACTION_PATH}/build_with_remote_buildkit.sh" bash -c "${COMMAND}" |
| 94 | + fi |
| 95 | +
|
| 96 | + args=() |
| 97 | +
|
| 98 | + # Tags may be newline- or comma-separated. |
| 99 | + first_tag="" |
| 100 | + while IFS= read -r tag; do |
| 101 | + tag="$(echo "${tag}" | tr -d '[:space:]')" |
| 102 | + [[ -z "${tag}" ]] && continue |
| 103 | + [[ -z "${first_tag}" ]] && first_tag="${tag}" |
| 104 | + args+=(--tag "${tag}") |
| 105 | + done < <(echo "${TAGS}" | tr ',' '\n') |
| 106 | + if [[ -z "${first_tag}" ]]; then |
| 107 | + echo "::error::no image tag was provided" >&2 |
| 108 | + exit 1 |
| 109 | + fi |
| 110 | + echo "image=${first_tag}" >> "${GITHUB_OUTPUT}" |
| 111 | +
|
| 112 | + # Remote BuildKit is per-arch, so the only buildable platform is the |
| 113 | + # runner's own. |
| 114 | + case "$(uname -m)" in |
| 115 | + aarch64|arm64) args+=(--platform linux/arm64) ;; |
| 116 | + *) args+=(--platform linux/amd64) ;; |
| 117 | + esac |
| 118 | +
|
| 119 | + [[ -n "${FILE}" ]] && args+=(--file "${FILE}") |
| 120 | + [[ -n "${TARGET}" ]] && args+=(--target "${TARGET}") |
| 121 | +
|
| 122 | + while IFS= read -r build_arg; do |
| 123 | + [[ -z "${build_arg}" ]] && continue |
| 124 | + args+=(--build-arg "${build_arg}") |
| 125 | + done <<< "${BUILD_ARGS}" |
| 126 | +
|
| 127 | + while IFS= read -r label; do |
| 128 | + [[ -z "${label}" ]] && continue |
| 129 | + args+=(--label "${label}") |
| 130 | + done <<< "${LABELS}" |
| 131 | +
|
| 132 | + while IFS= read -r cache; do |
| 133 | + [[ -z "${cache}" ]] && continue |
| 134 | + args+=(--cache-from "${cache}") |
| 135 | + done <<< "${CACHE_FROM}" |
| 136 | +
|
| 137 | + while IFS= read -r cache; do |
| 138 | + [[ -z "${cache}" ]] && continue |
| 139 | + args+=(--cache-to "${cache}") |
| 140 | + done <<< "${CACHE_TO}" |
| 141 | +
|
| 142 | + if [[ "${PUSH}" == "true" ]]; then |
| 143 | + args+=(--push) |
| 144 | + else |
| 145 | + echo "::warning::push=false — the image is built but discarded (no local docker daemon to --load into)" |
| 146 | + fi |
| 147 | +
|
| 148 | + bash "${GITHUB_ACTION_PATH}/build_with_remote_buildkit.sh" \ |
| 149 | + docker buildx build "${args[@]}" "${CONTEXT}" |
0 commit comments