release: prepare v0.3.0 — version bump, curated changelog, dashboard … #69
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
| # ============================================================================= | |
| # Publish Tenant Images — proxy + dashboard images for the multi-tenant | |
| # Basilica lifecycle. | |
| # | |
| # Unlike release.yml (tag-triggered, cuts a real release + crates.io + PyPI), | |
| # this workflow only builds + pushes container images to GHCR under the | |
| # techlab-innov org. Two trigger paths: | |
| # | |
| # - push to main → publish `:main` and `:sha-<short>` tags (auto) | |
| # - workflow_dispatch → optionally tag as `:latest` and/or a custom label | |
| # | |
| # The product's `tenant-lifecycle.yml` workflow references the published | |
| # images via the per-tenant config file (image: ghcr.io/techlab-innov/...). | |
| # ============================================================================= | |
| name: publish-images | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - "crates/**" | |
| - "Cargo.toml" | |
| - "Cargo.lock" | |
| - "Dockerfile" | |
| - "dashboard/**" | |
| - ".github/workflows/publish-images.yml" | |
| # Baked into the proxy image — a change alters image content but is not | |
| # under crates/** or dashboard/**, so it must trigger a rebuild too. | |
| - "config.example.yaml" # Dockerfile: COPY config.example.yaml /etc/llmtrace/config.yaml | |
| - "benchmarks/**" # Dockerfile: COPY benchmarks/ (workspace member built into the proxy binary) | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Additional image tag to publish (e.g. 'latest', 'rc1')" | |
| required: false | |
| type: string | |
| default: "latest" | |
| mark_latest: | |
| description: "Publish :latest alongside the sha + tag" | |
| required: false | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: read | |
| packages: write | |
| # Serialise concurrent publish runs per ref so a slow / hung build doesn't | |
| # pile up behind itself. `cancel-in-progress: false` because each pushed | |
| # commit deserves its own `:sha-<x>` tag — we don't want to skip SHAs. | |
| # Combined with the per-job `timeout-minutes` below, a hung older build | |
| # fails fast and frees the slot for the next one within the timeout | |
| # window rather than blocking indefinitely (the old default was 360min | |
| # but real-world hangs in this workflow exceeded 9h before manual | |
| # cancellation in the 2026-05-25 incident). | |
| concurrency: | |
| group: publish-images-${{ github.ref }}-${{ github.event_name }} | |
| cancel-in-progress: false | |
| env: | |
| REGISTRY: ghcr.io | |
| OWNER: techlab-innov | |
| jobs: | |
| proxy: | |
| name: Proxy image | |
| runs-on: ubuntu-latest | |
| # Hard cap so a hung buildx / QEMU multi-arch step fails fast instead | |
| # of holding the slot indefinitely. Historical good runs complete in | |
| # well under 30 min. See incident notes in the concurrency block above. | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 | |
| - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute tags | |
| id: tags | |
| env: | |
| MARK_LATEST: ${{ inputs.mark_latest }} | |
| CUSTOM_TAG: ${{ inputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| image="${REGISTRY}/${OWNER}/llmtrace-proxy" | |
| short_sha="${GITHUB_SHA::7}" | |
| tags=("${image}:sha-${short_sha}" "${image}:main") | |
| if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then | |
| [[ -n "${CUSTOM_TAG}" ]] && tags+=("${image}:${CUSTOM_TAG}") | |
| [[ "${MARK_LATEST}" == "true" ]] && tags+=("${image}:latest") | |
| else | |
| # Auto push from main always refreshes :latest | |
| tags+=("${image}:latest") | |
| fi | |
| printf 'tags=' >> "${GITHUB_OUTPUT}" | |
| printf '%s,' "${tags[@]}" | sed 's/,$//' >> "${GITHUB_OUTPUT}" | |
| printf '\n' >> "${GITHUB_OUTPUT}" | |
| printf 'tags computed:\n'; printf ' %s\n' "${tags[@]}" | |
| - name: Build and push | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| push: true | |
| # linux/amd64 only. QEMU emulation of arm64 for the Rust release | |
| # build hung repeatedly in the 2026-05-25 incident — three | |
| # consecutive runs failed to complete the proxy image (one ran | |
| # 9h before manual cancellation, two more 30-min cancellations | |
| # after retry). Basilica's k8s nodes are amd64; arm64 is | |
| # currently unused. Re-add `linux/arm64` only with a native | |
| # arm64 runner — not via QEMU. | |
| platforms: linux/amd64 | |
| tags: ${{ steps.tags.outputs.tags }} | |
| labels: | | |
| org.opencontainers.image.source=https://github.com/${{ github.repository }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.created=${{ github.event.head_commit.timestamp }} | |
| cache-from: type=gha,scope=proxy | |
| cache-to: type=gha,mode=max,scope=proxy | |
| dashboard: | |
| name: Dashboard image | |
| runs-on: ubuntu-latest | |
| # Same hard cap as the proxy job — see notes above. | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 | |
| - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute tags | |
| id: tags | |
| env: | |
| MARK_LATEST: ${{ inputs.mark_latest }} | |
| CUSTOM_TAG: ${{ inputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| image="${REGISTRY}/${OWNER}/llmtrace-dashboard" | |
| short_sha="${GITHUB_SHA::7}" | |
| tags=("${image}:sha-${short_sha}" "${image}:main") | |
| if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then | |
| [[ -n "${CUSTOM_TAG}" ]] && tags+=("${image}:${CUSTOM_TAG}") | |
| [[ "${MARK_LATEST}" == "true" ]] && tags+=("${image}:latest") | |
| else | |
| tags+=("${image}:latest") | |
| fi | |
| printf 'tags=' >> "${GITHUB_OUTPUT}" | |
| printf '%s,' "${tags[@]}" | sed 's/,$//' >> "${GITHUB_OUTPUT}" | |
| printf '\n' >> "${GITHUB_OUTPUT}" | |
| printf 'tags computed:\n'; printf ' %s\n' "${tags[@]}" | |
| - name: Build and push | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: ./dashboard | |
| file: ./dashboard/Dockerfile | |
| push: true | |
| # linux/amd64 only — for symmetry with the proxy image (see notes | |
| # above). Dashboard Next.js builds completed fine on multi-arch | |
| # (~23s), but a mixed-arch deployment is the worst of both worlds | |
| # so we drop arm64 across the board until a native arm64 runner | |
| # is available. | |
| platforms: linux/amd64 | |
| tags: ${{ steps.tags.outputs.tags }} | |
| labels: | | |
| org.opencontainers.image.source=https://github.com/${{ github.repository }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.created=${{ github.event.head_commit.timestamp }} | |
| cache-from: type=gha,scope=dashboard | |
| cache-to: type=gha,mode=max,scope=dashboard |