Release on-prem image to GHCR #4
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release on-prem image to GHCR | |
| # WS2-1 (DRAFT) — build the core images on a Linux runner and publish them | |
| # to GHCR so the local on-prem Talos cluster can consume them. | |
| # | |
| # WHY this exists, separate from release-staging-image.yml: | |
| # - That workflow pushes to ECR via GitHub OIDC, and the landing-zone IAM | |
| # trust pins its job_workflow_ref. This one touches NO AWS — it authes to | |
| # GHCR with the built-in GITHUB_TOKEN (packages: write) — so it does not | |
| # widen any AWS trust scope. | |
| # - The local-first WS2 path has no ECR (accounts are at zero) and CI cannot | |
| # reach the in-cluster registry. GHCR is the CI -> local transport: CI | |
| # publishes here, the operator crane-pulls and re-pushes into the cluster's | |
| # registry:2. (See the WS2 plan on aegis-platform-aws#46.) | |
| # | |
| # WHY a native arm64 runner: | |
| # The local Talos cluster runs on apple/container = linux/arm64. An amd64 | |
| # image (the ubuntu-latest default) would not run there. ubuntu-24.04-arm is | |
| # GitHub's free arm64 Linux runner for public repos. local_config_cc | |
| # auto-detects the runner's arm64 GCC — no toolchain / sysroot setup, no | |
| # BUILD changes. This is also why the engine build belongs in CI, not on the | |
| # 16GB dev host: the grpc + BoringSSL + whisper.cpp/ggml/llama.cpp link line | |
| # needs more RAM than that host can spare for a build VM (de-risked 2026-06-16). | |
| # | |
| # RUNTIME-BASE glibc must be >= the build glibc (resolved 2026-06-16, WS2-2): | |
| # The engine binary is dynamically linked and local_config_cc is non-hermetic, | |
| # so it inherits the runner's glibc (ubuntu-24.04-arm = glibc 2.39, GCC 13 / | |
| # GLIBCXX_3.4.32). glibc is forward-compatible, not backward — the runtime | |
| # base must therefore ship glibc >= 2.39. distroless cc-debian12 (glibc 2.36) | |
| # was too old: the engine crashed at startup with `GLIBC_2.38 not found` / | |
| # `GLIBCXX_3.4.32 not found`. The base is now cc-debian13 (Debian 13, glibc | |
| # 2.41 / GCC 14) — see packaging/engine/BUILD.bazel + MODULE.bazel. | |
| # | |
| # Manual trigger only for now (local-first; the operator pulls on demand). A | |
| # push trigger can be added once the WS2 on-prem flow stabilises. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| push_gateway: | |
| description: "Also build + push the gateway image (Go; builds fast)" | |
| type: boolean | |
| default: true | |
| # GHCR auth uses the built-in token. No id-token / AWS here. | |
| permissions: | |
| contents: read | |
| packages: write | |
| env: | |
| # GHCR repos (lowercase owner required). The image type lives in the repo | |
| # name rather than a tag prefix — GHCR is one package per name. | |
| GHCR_ENGINE: ghcr.io/binhsu/aegis-core-engine | |
| GHCR_GATEWAY: ghcr.io/binhsu/aegis-core-gateway | |
| jobs: | |
| push-onprem-image: | |
| name: Build + push core images to GHCR (linux/arm64) | |
| runs-on: ubuntu-24.04-arm | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| # Same cache shape as release-staging-image.yml. Note the cache key is | |
| # NOT arch-qualified there; this workflow is the only arm64 builder, so | |
| # give it its own key prefix to avoid cross-arch artifact mixing. | |
| - name: Cache Bazel | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ${{ github.workspace }}/.bazel_cache | |
| ${{ github.workspace }}/.bazelisk | |
| /tmp/aegis-bazel-* | |
| key: bazel-onprem-arm64-${{ hashFiles('MODULE.bazel', 'MODULE.bazel.lock', '.bazelversion', '.bazelrc') }} | |
| restore-keys: | | |
| bazel-onprem-arm64- | |
| # Optional remote cache, mirrors ci-baseline.yml. Degrades to local | |
| # execution when the secret is absent (forks). | |
| - name: Configure BuildBuddy remote cache | |
| env: | |
| BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} | |
| run: | | |
| if [ -z "${BUILDBUDDY_API_KEY:-}" ]; then | |
| echo "BUILDBUDDY_API_KEY not set — skipping remote cache (normal in forks)" | |
| exit 0 | |
| fi | |
| echo "::add-mask::$BUILDBUDDY_API_KEY" | |
| cat > .bazelrc.user <<EOF | |
| build --remote_cache=grpcs://remote.buildbuddy.io | |
| build --remote_header=x-buildbuddy-api-key=$BUILDBUDDY_API_KEY | |
| build --bes_backend=grpcs://remote.buildbuddy.io | |
| build --bes_results_url=https://app.buildbuddy.io/invocation/ | |
| build --remote_timeout=3600 | |
| EOF | |
| # rules_oci's oci_push reads ~/.docker/config.json. Log in to GHCR so the | |
| # subsequent bazel run authenticates automatically. | |
| - name: Log in to GHCR | |
| run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin | |
| - name: Build engine OCI image (linux/arm64) | |
| run: ./tools/bazelisk/bazelisk build //packaging/engine:image | |
| # Reuse the existing push_staging rule; --repository / --tag are runtime | |
| # overrides (the BUILD default points at ECR and is never used here). No | |
| # engine smoke step: the engine loads a ggml model from /models at start | |
| # and crashes without it (engine_cpp/cmd/engine/main.cc) — the model | |
| # arrives via MinIO in WS2-2. Deliverable = builds + pushes by digest. | |
| - name: Push engine image to GHCR | |
| id: push-engine | |
| run: | | |
| set -euxo pipefail | |
| TAG="onprem-${GITHUB_SHA}" | |
| ./tools/bazelisk/bazelisk run //packaging/engine:push_staging -- \ | |
| --repository "${GHCR_ENGINE}" \ | |
| --tag "${TAG}" 2>&1 | tee /tmp/engine-push.log | |
| DIGEST=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/engine-push.log | tail -1) | |
| echo "ref=${GHCR_ENGINE}@${DIGEST}" >> "$GITHUB_OUTPUT" | |
| echo "::notice title=Engine image pushed::${GHCR_ENGINE}:${TAG} (${DIGEST})" | |
| - name: Build gateway OCI image (linux/arm64) | |
| if: ${{ inputs.push_gateway }} | |
| run: ./tools/bazelisk/bazelisk build //packaging/gateway:image | |
| - name: Push gateway image to GHCR | |
| id: push-gateway | |
| if: ${{ inputs.push_gateway }} | |
| run: | | |
| set -euxo pipefail | |
| TAG="onprem-${GITHUB_SHA}" | |
| ./tools/bazelisk/bazelisk run //packaging/gateway:push_staging -- \ | |
| --repository "${GHCR_GATEWAY}" \ | |
| --tag "${TAG}" 2>&1 | tee /tmp/gateway-push.log | |
| DIGEST=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/gateway-push.log | tail -1) | |
| echo "ref=${GHCR_GATEWAY}@${DIGEST}" >> "$GITHUB_OUTPUT" | |
| echo "::notice title=Gateway image pushed::${GHCR_GATEWAY}:${TAG} (${DIGEST})" | |
| - name: Summary | |
| run: | | |
| { | |
| echo "## Core images pushed to GHCR (linux/arm64)" | |
| echo "- engine: \`${{ steps.push-engine.outputs.ref }}\`" | |
| if [ "${{ inputs.push_gateway }}" = "true" ]; then | |
| echo "- gateway: \`${{ steps.push-gateway.outputs.ref }}\`" | |
| fi | |
| echo "" | |
| echo "Pull into the local cluster registry, e.g.:" | |
| echo '```' | |
| echo "crane copy ${{ steps.push-engine.outputs.ref }} <node-ip>:5000/aegis-core@<digest>" | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" |