name: Build and Push Docker Images on: push: branches: [main, master] release: types: [published] pull_request: branches: [main, master] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} ZIG_VERSION: 0.15.2 permissions: contents: read packages: write jobs: # Build AMD64 image on Linux x86_64 runner build-amd64: name: Build Docker Image (linux/amd64) runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout uses: actions/checkout@v5 with: fetch-depth: 0 # Ensure tags are available for version derivation submodules: false # Submodules not needed for Docker build - name: Initialize required submodules shell: bash run: | set -euo pipefail retry_cmd() { local attempts="$1" shift local attempt=1 until "$@"; do if [ "$attempt" -ge "$attempts" ]; then return 1 fi echo "Command failed (attempt ${attempt}/${attempts}): $*" >&2 sleep $((attempt * 5)) attempt=$((attempt + 1)) done } git config --global url."https://github.com/".insteadOf "git@github.com:" retry_cmd 4 git submodule update --init --depth 1 third_party/sqlite-vec-cpp retry_cmd 4 git submodule update --init --depth 1 third_party/simeon retry_cmd 4 git submodule update --init --depth 1 third_party/symspell - name: Determine version id: version shell: bash run: | if [[ "${{ github.event_name }}" == "release" ]]; then VERSION="${{ github.event.release.tag_name }}" VERSION="${VERSION#v}" else VERSION="dev-$(git rev-parse --short HEAD)" fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Determined version: $VERSION" - name: Verify Docker daemon is running run: | if ! docker info >/dev/null 2>&1; then echo "Docker daemon is not available. Ensure Docker is installed and running, and the runner user has permission to access /var/run/docker.sock." exit 1 fi - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Container Registry if: github.event_name != 'pull_request' && env.ACT != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} flavor: | suffix=-amd64 tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=raw,value=latest,enable={{is_default_branch}} type=raw,value=${{ steps.version.outputs.version }} - name: Compute tags (local fallback) id: tags shell: bash run: | if [[ "${ACT:-}" == "true" ]]; then T="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:local-amd64" else T="${{ steps.meta.outputs.tags }}" # Guard against invalid tags generated under limited local metadata if [[ -z "$T" || "$T" == *":-amd64" || "$T" =~ :$ ]]; then V="${{ steps.version.outputs.version }}" if [[ -z "$V" ]]; then V="dev-${GITHUB_SHA::7}"; fi T="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${V}-amd64" fi fi # Write possibly multi-line tag list safely to GITHUB_OUTPUT { echo "tags<<__TAGS__" echo "$T" echo "__TAGS__" } >> "$GITHUB_OUTPUT" - name: Build and push AMD64 Docker image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile platforms: linux/amd64 push: ${{ env.ACT != 'true' && github.event_name != 'pull_request' }} load: ${{ env.ACT == 'true' }} tags: ${{ steps.tags.outputs.tags }} labels: | org.opencontainers.image.title=YAMS org.opencontainers.image.description=Yet Another Memory System - High-performance content-addressed storage org.opencontainers.image.url=https://github.com/${{ github.repository }} org.opencontainers.image.source=https://github.com/${{ github.repository }} org.opencontainers.image.version=${{ steps.version.outputs.version }} org.opencontainers.image.revision=${{ github.sha }} org.opencontainers.image.architecture=amd64 build-args: | YAMS_VERSION=${{ steps.version.outputs.version }} GIT_COMMIT=${{ github.sha }} GIT_TAG=${{ github.ref_name }} ZIG_VERSION=${{ env.ZIG_VERSION }} # Use GitHub Actions cache for BuildKit layers (scoped per arch) cache-from: type=gha,scope=docker-${{ runner.os }}-amd64 cache-to: type=gha,mode=max,scope=docker-${{ runner.os }}-amd64 # Enable BuildKit inline caching and leverage Conan layer reuse; future enhancement could mount host cache provenance: false # Build ARM64 image on native Linux ARM64 runner (no QEMU) build-arm64: name: Build Docker Image (linux/arm64 native) runs-on: ubuntu-24.04-arm steps: - name: Checkout uses: actions/checkout@v5 with: fetch-depth: 0 submodules: false - name: Initialize required submodules shell: bash run: | set -euo pipefail retry_cmd() { local attempts="$1" shift local attempt=1 until "$@"; do if [ "$attempt" -ge "$attempts" ]; then return 1 fi echo "Command failed (attempt ${attempt}/${attempts}): $*" >&2 sleep $((attempt * 5)) attempt=$((attempt + 1)) done } git config --global url."https://github.com/".insteadOf "git@github.com:" retry_cmd 4 git submodule update --init --depth 1 third_party/sqlite-vec-cpp retry_cmd 4 git submodule update --init --depth 1 third_party/simeon retry_cmd 4 git submodule update --init --depth 1 third_party/symspell - name: Verify Docker daemon is running run: | if ! docker info >/dev/null 2>&1; then echo "Docker daemon is not available. Ensure Docker is installed and running, and the runner user has permission to access /var/run/docker.sock." exit 1 fi - name: Determine version id: version shell: bash run: | if [[ "${{ github.event_name }}" == "release" ]]; then VERSION="${{ github.event.release.tag_name }}" VERSION="${VERSION#v}" else VERSION="dev-$(git rev-parse --short HEAD)" fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Determined version: $VERSION" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: driver-opts: | image=moby/buildkit:latest - name: Log in to Container Registry if: github.event_name != 'pull_request' && env.ACT != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} flavor: | suffix=-arm64 tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=raw,value=latest,enable={{is_default_branch}} type=raw,value=${{ steps.version.outputs.version }} - name: Compute tags (local fallback) id: tags shell: bash run: | if [[ "${ACT:-}" == "true" ]]; then T="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:local-arm64" else T="${{ steps.meta.outputs.tags }}" # Guard against invalid tags generated under limited local metadata if [[ -z "$T" || "$T" == *":-arm64" || "$T" =~ :$ ]]; then V="${{ steps.version.outputs.version }}" if [[ -z "$V" ]]; then V="dev-${GITHUB_SHA::7}"; fi T="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${V}-arm64" fi fi # Write possibly multi-line tag list safely to GITHUB_OUTPUT { echo "tags<<__TAGS__" echo "$T" echo "__TAGS__" } >> "$GITHUB_OUTPUT" - name: Build and push ARM64 Docker image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile platforms: linux/arm64 push: ${{ env.ACT != 'true' && github.event_name != 'pull_request' }} load: ${{ env.ACT == 'true' }} tags: ${{ steps.tags.outputs.tags }} labels: | org.opencontainers.image.title=YAMS org.opencontainers.image.description=Yet Another Memory System - High-performance content-addressed storage org.opencontainers.image.url=https://github.com/${{ github.repository }} org.opencontainers.image.source=https://github.com/${{ github.repository }} org.opencontainers.image.version=${{ steps.version.outputs.version }} org.opencontainers.image.revision=${{ github.sha }} org.opencontainers.image.architecture=arm64 build-args: | YAMS_VERSION=${{ steps.version.outputs.version }} GIT_COMMIT=${{ github.sha }} GIT_TAG=${{ github.ref_name }} ZIG_VERSION=${{ env.ZIG_VERSION }} # Use GitHub Actions cache for BuildKit layers (scoped per arch) cache-from: type=gha,scope=docker-${{ runner.os }}-arm64 cache-to: type=gha,mode=max,scope=docker-${{ runner.os }}-arm64 provenance: false # Create multi-arch manifest create-manifest: name: Create Multi-arch Manifest needs: [build-amd64, build-arm64] runs-on: ubuntu-latest if: github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Container Registry shell: bash env: REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail for attempt in 1 2 3; do if printf '%s' "$REGISTRY_TOKEN" | docker login "${{ env.REGISTRY }}" -u "${{ github.actor }}" --password-stdin; then exit 0 fi if [ "$attempt" -eq 3 ]; then echo "docker login to ${{ env.REGISTRY }} failed after ${attempt} attempts" >&2 exit 1 fi sleep $((attempt * 10)) done - name: Create and push manifest env: VERSION: ${{ needs.build-amd64.outputs.version }} run: | # Base image name IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" # For main branch pushes, create 'latest' manifest if [[ "${{ github.event_name }}" == "push" && ("${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/master") ]]; then docker buildx imagetools create -t ${IMAGE}:latest \ ${IMAGE}:latest-amd64 \ ${IMAGE}:latest-arm64 echo "Created multi-arch manifest: ${IMAGE}:latest" fi # For releases, create versioned manifests if [[ "${{ github.event_name }}" == "release" ]]; then TAG="${{ github.event.release.tag_name }}" TAG="${TAG#v}" # Create manifest for full version docker buildx imagetools create -t ${IMAGE}:${TAG} \ ${IMAGE}:${TAG}-amd64 \ ${IMAGE}:${TAG}-arm64 echo "Created multi-arch manifest: ${IMAGE}:${TAG}" # Also create major.minor manifest MAJOR_MINOR=$(echo ${TAG} | sed -E 's/^([0-9]+\.[0-9]+).*/\1/') if [[ "${MAJOR_MINOR}" != "${TAG}" ]]; then docker buildx imagetools create -t ${IMAGE}:${MAJOR_MINOR} \ ${IMAGE}:${TAG}-amd64 \ ${IMAGE}:${TAG}-arm64 echo "Created multi-arch manifest: ${IMAGE}:${MAJOR_MINOR}" fi # And major-only manifest MAJOR=$(echo ${TAG} | sed -E 's/^([0-9]+).*/\1/') if [[ "${MAJOR}" != "${TAG}" ]]; then docker buildx imagetools create -t ${IMAGE}:${MAJOR} \ ${IMAGE}:${TAG}-amd64 \ ${IMAGE}:${TAG}-arm64 echo "Created multi-arch manifest: ${IMAGE}:${MAJOR}" fi fi # For dev builds if [[ "$VERSION" == dev-* ]]; then docker buildx imagetools create -t ${IMAGE}:${VERSION} \ ${IMAGE}:${VERSION}-amd64 \ ${IMAGE}:${VERSION}-arm64 echo "Created multi-arch manifest: ${IMAGE}:${VERSION}" fi - name: Test multi-arch image run: | # Test that the multi-arch image works if [[ "${{ github.event_name }}" == "push" && ("${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/master") ]]; then docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest --version echo "Multi-arch image test completed successfully" elif [[ "${{ github.event_name }}" == "release" ]]; then TAG="${{ github.event.release.tag_name }}" TAG="${TAG#v}" docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG} --version echo "Multi-arch image test completed successfully" fi