name: Publish container image # Build and publish the omnigraph-server container image to GHCR so # downstream deployments can pull it without building from source. # # Kept separate from release.yml on purpose: an image-publish failure must # not block the binary release / Homebrew chain, and a manual backfill of an # old tag must not re-run the four-platform release matrix. # # Triggers (same dual pattern as release.yml): # - push of a v* tag (normal release) # - workflow_dispatch with an explicit `tag` (publish an image for a past # tag; resolves the same `${{ inputs.tag || github.ref_name }}`) # # The binaries are compiled inside a rust:1-bookworm container, NOT on the # runner host: the Dockerfile's runtime base is debian bookworm-slim # (glibc 2.36), and binaries built on ubuntu-latest (glibc 2.39) do not run # there. Builder and runtime must share the same distro release. on: push: tags: - "v*" workflow_dispatch: inputs: tag: description: "Tag to publish an image for (e.g. v0.8.1). Required for manual dispatches." required: true type: string jobs: publish_image: name: Build and push image runs-on: ubuntu-latest permissions: contents: read packages: write env: RELEASE_TAG: ${{ inputs.tag || github.ref_name }} steps: # The dispatch `tag` input is free-form; without this guard a dispatcher # could pass a branch name or SHA and publish non-release code under a # release-looking image tag. Require an existing v* tag ref. - name: Validate dispatch tag if: github.event_name == 'workflow_dispatch' env: GH_TOKEN: ${{ github.token }} run: | [[ "${RELEASE_TAG}" == v* ]] \ || { echo "tag must be a v* release tag, got: ${RELEASE_TAG}" >&2; exit 1; } gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" >/dev/null \ || { echo "no such tag: ${RELEASE_TAG}" >&2; exit 1; } - name: Checkout source uses: actions/checkout@v5.0.1 with: # Fully qualified so a branch of the same name can never shadow the tag. ref: refs/tags/${{ env.RELEASE_TAG }} - name: Build release binaries (bookworm builder) run: | docker run --rm -v "$PWD:/work" -w /work rust:1-bookworm bash -euxc ' apt-get update apt-get install -y --no-install-recommends protobuf-compiler libprotobuf-dev # --features omnigraph-server/aws: include the AWS Secrets Manager # bearer-token source so one public image serves both token modes. cargo build --release --locked -p omnigraph-cli -p omnigraph-server --features omnigraph-server/aws ' - name: Log in to GHCR run: | echo "${{ secrets.GITHUB_TOKEN }}" \ | docker login ghcr.io --username "${{ github.actor }}" --password-stdin - name: Build and push image run: | owner="${GITHUB_REPOSITORY_OWNER,,}" image="ghcr.io/${owner}/omnigraph-server" docker build -t "${image}:${RELEASE_TAG}" . docker push "${image}:${RELEASE_TAG}" # Move `latest` only on real tag pushes — a dispatch backfill of an # old tag must not repoint `latest` at it. if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then docker tag "${image}:${RELEASE_TAG}" "${image}:latest" docker push "${image}:latest" fi - name: Report pinned digest run: | owner="${GITHUB_REPOSITORY_OWNER,,}" image="ghcr.io/${owner}/omnigraph-server" digest=$(docker inspect --format='{{index .RepoDigests 0}}' "${image}:${RELEASE_TAG}") { echo "## Container image published" echo echo '```' echo "${image}:${RELEASE_TAG}" echo "${digest}" echo '```' echo echo "Pin deployments to the digest form." } >> "$GITHUB_STEP_SUMMARY"