diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 000000000..3afeed449 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,226 @@ +name: Docker Publish + +on: + push: + branches: + - master + paths: + - 'Dockerfile' + - 'Makefile' + - '.github/workflows/docker-publish.yml' + pull_request: + paths: + - 'Dockerfile' + - 'Makefile' + - '.github/workflows/docker-publish.yml' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + PG_MAJOR: 17 + DEBIAN_CODENAME: bookworm + +jobs: + metadata: + runs-on: ubuntu-24.04 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout code + uses: actions/checkout@v7.0.1 + + - name: Resolve pgvector version + id: version + shell: bash + run: | + set -euo pipefail + + version="$(awk '$1 == "EXTVERSION" && $2 == "=" { print $3; exit }' Makefile)" + dockerfile_version="$(sed -n 's|.*pgvector.git#v\([^[:space:]]*\).*|\1|p' Dockerfile)" + + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid pgvector version from Makefile: $version" >&2 + exit 1 + fi + + if [ "$version" != "$dockerfile_version" ]; then + echo "Makefile version $version does not match Dockerfile version $dockerfile_version" >&2 + exit 1 + fi + + echo "version=$version" >> "$GITHUB_OUTPUT" + + validate: + needs: metadata + if: github.event_name == 'pull_request' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - name: Checkout code + uses: actions/checkout@v7.0.1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build amd64 image + uses: docker/build-push-action@v7 + with: + context: . + platforms: linux/amd64 + push: false + pull: true + no-cache: true + build-args: | + PG_MAJOR=${{ env.PG_MAJOR }} + DEBIAN_CODENAME=${{ env.DEBIAN_CODENAME }} + labels: | + org.opencontainers.image.source=${{ github.event.repository.html_url }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.version=${{ needs.metadata.outputs.version }} + + build: + needs: metadata + if: github.event_name != 'pull_request' + permissions: + contents: read + packages: write + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + arch: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + steps: + - name: Checkout code + uses: actions/checkout@v7.0.1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image by digest + id: build + uses: docker/build-push-action@v7 + with: + context: . + platforms: ${{ matrix.platform }} + pull: true + no-cache: true + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + build-args: | + PG_MAJOR=${{ env.PG_MAJOR }} + DEBIAN_CODENAME=${{ env.DEBIAN_CODENAME }} + labels: | + org.opencontainers.image.source=${{ github.event.repository.html_url }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.version=${{ needs.metadata.outputs.version }} + + - name: Export digest + shell: bash + run: | + set -euo pipefail + + digest="${{ steps.build.outputs.digest }}" + if [[ ! "$digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then + echo "Build returned an invalid image digest: $digest" >&2 + exit 1 + fi + + mkdir -p /tmp/digests + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v7 + with: + name: digest-${{ matrix.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + publish: + needs: [metadata, build] + if: github.event_name != 'pull_request' + permissions: + contents: read + packages: write + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Download digests + uses: actions/download-artifact@v8.0.1 + with: + path: /tmp/digests + pattern: digest-* + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish multi-arch manifest + env: + PGVECTOR_VERSION: ${{ needs.metadata.outputs.version }} + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + + image="${REGISTRY}/${IMAGE_NAME}" + refs=() + for digest_file in /tmp/digests/digest-*/*; do + digest="$(basename "$digest_file")" + refs+=("${image}@sha256:${digest}") + done + + if [ "${#refs[@]}" -ne 2 ]; then + echo "Expected two platform digests, found ${#refs[@]}" >&2 + exit 1 + fi + + docker buildx imagetools create \ + --tag "${image}:pg${PG_MAJOR}" \ + --tag "${image}:pg${PG_MAJOR}-${DEBIAN_CODENAME}" \ + --tag "${image}:${PGVECTOR_VERSION}-pg${PG_MAJOR}" \ + --tag "${image}:${PGVECTOR_VERSION}-pg${PG_MAJOR}-${DEBIAN_CODENAME}" \ + "${refs[@]}" + + - name: Verify published platforms + shell: bash + run: | + set -euo pipefail + + image="${REGISTRY}/${IMAGE_NAME}:pg${PG_MAJOR}" + manifest="$(docker buildx imagetools inspect --raw "$image")" + + for arch in amd64 arm64; do + jq -e --arg arch "$arch" \ + 'any(.manifests[]; .platform.os == "linux" and .platform.architecture == $arch)' \ + <<< "$manifest" > /dev/null + done + + docker buildx imagetools inspect "$image"