name: Docker Release Build
on:
release:
types: [published]
workflow_run:
workflows: ["Release on Tag"]
types:
- completed
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., v0.1.0-beta.1)'
required: true
default: 'v0.1.0-beta.1'
permissions:
contents: read
packages: write
env:
REGISTRY: docker.io
OWNER_LOWER: falkordb
CYPHER_SKILLS_REF: 172978316e493c48ca352a0be6fb668a9f728855
jobs:
docker-build:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'release' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run' }}
outputs:
release_tag: ${{ steps.extract_version.outputs.release_tag }}
version: ${{ steps.extract_version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Normalize Docker owner
run: echo "OWNER_LOWER=$(printf '%s' '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Extract release version
id: extract_version
run: |
echo "๐ Debug: Event context information:"
echo " Event name: '${{ github.event_name }}'"
echo " Head branch: '${{ github.event.workflow_run.head_branch }}'"
echo " Head SHA: '${{ github.event.workflow_run.head_sha }}'"
echo " Conclusion: '${{ github.event.workflow_run.conclusion }}'"
# Extract version from different trigger types
if [ "${{ github.event_name }}" = "release" ]; then
RELEASE_TAG="${{ github.event.release.tag_name }}"
echo "๐ Using release tag: $RELEASE_TAG"
elif [ "${{ github.event_name }}" = "workflow_run" ]; then
# For workflow_run, we need to check if it was triggered by a tag
# The head_branch might be the tag name, or we might need to look at the head_sha
HEAD_BRANCH="${{ github.event.workflow_run.head_branch }}"
echo "๐ Checking head_branch: '$HEAD_BRANCH'"
if [[ "$HEAD_BRANCH" =~ ^v[0-9] ]]; then
RELEASE_TAG="$HEAD_BRANCH"
echo "โ
Found tag in head_branch: $RELEASE_TAG"
else
# Try to get tag from GitHub API using the head SHA
HEAD_SHA="${{ github.event.workflow_run.head_sha }}"
echo "๐ Checking if SHA $HEAD_SHA corresponds to a tag..."
# Use GitHub API to find tags pointing to this SHA
echo "๐ Fetching all tags from GitHub API..."
TAG_RESPONSE=$(curl -s -H "Authorization: token ${{ github.token }}" \
"https://api.github.com/repos/${{ github.repository }}/git/refs/tags")
echo "๐ Tag API response sample:"
echo "$TAG_RESPONSE" | jq -r '.[0:3][] | {ref: .ref, sha: .object.sha}' 2>/dev/null || echo "Failed to parse tag response"
# Find tag that points to our SHA
RELEASE_TAG=$(echo "$TAG_RESPONSE" | jq -r --arg sha "$HEAD_SHA" \
'.[] | select(.object.sha == $sha) | .ref | sub("refs/tags/"; "")')
echo "๐ Tag lookup result: '$RELEASE_TAG'"
if [ -n "$RELEASE_TAG" ] && [ "$RELEASE_TAG" != "null" ] && [ "$RELEASE_TAG" != "" ]; then
echo "โ
Found tag for SHA: $RELEASE_TAG"
else
echo "โ No tag found for SHA $HEAD_SHA"
echo "๐ Available tags (first 5):"
echo "$TAG_RESPONSE" | jq -r '.[0:5][] | "\(.ref) -> \(.object.sha)"' 2>/dev/null || echo "Failed to list tags"
echo "โ Skipping Docker build - no tag detected"
exit 0
fi
fi
elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref_type }}" = "tag" ]; then
RELEASE_TAG="${{ github.ref_name }}"
echo "๐ Using push tag: $RELEASE_TAG"
else
RELEASE_TAG="${{ github.event.inputs.version }}"
echo "๐ Using manual input: $RELEASE_TAG"
fi
# Only proceed if workflow_run was successful (when applicable)
if [ "${{ github.event_name }}" = "workflow_run" ] && [ "${{ github.event.workflow_run.conclusion }}" != "success" ]; then
echo "Build workflow did not complete successfully, skipping Docker build"
exit 0
fi
# Validate that we have a release tag
if [ -z "$RELEASE_TAG" ] || [ "$RELEASE_TAG" = "null" ]; then
echo "โ No valid release tag found: '$RELEASE_TAG'"
exit 0
fi
echo "โ
Validated release tag: '$RELEASE_TAG'"
VERSION="${RELEASE_TAG#v}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
echo "Building Docker image for release: ${RELEASE_TAG}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a
- name: Log in to Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
continue-on-error: false
id: docker-login
- name: Make docker-build.sh executable
run: chmod +x ./docker-build.sh
- name: Wait for release assets to be available
run: |
echo "๐ Starting asset availability check..."
VERSION="${{ steps.extract_version.outputs.version }}"
RELEASE_TAG="${{ steps.extract_version.outputs.release_tag }}"
echo "๐ Debug: Version information:"
echo " Raw release_tag: '${{ steps.extract_version.outputs.release_tag }}'"
echo " Clean version: '${{ steps.extract_version.outputs.version }}'"
echo " Using VERSION: '${VERSION}'"
echo " Using RELEASE_TAG: '${RELEASE_TAG}'"
echo " Event name: '${{ github.event_name }}'"
# Fix: Use the clean version without 'v' prefix for URL construction
CLEAN_VERSION="${VERSION}"
echo " Clean version for URLs: '${CLEAN_VERSION}'"
# Enhanced asset checking with build workflow status monitoring
MAX_ATTEMPTS=60 # 30 minutes total (60 ร 30 seconds)
ATTEMPT=1
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Checking release assets availability..."
# Check build workflow status first
if [ "${{ github.event_name }}" = "workflow_run" ]; then
BUILD_STATUS="${{ github.event.workflow_run.conclusion }}"
echo "๐ Build workflow status: $BUILD_STATUS"
if [ "$BUILD_STATUS" = "failure" ] || [ "$BUILD_STATUS" = "cancelled" ]; then
echo "โ Build workflow failed/cancelled - no assets will be available"
exit 1
fi
fi
# Check if both required assets exist (root level .tar.gz files with templates)
X86_URL="https://github.com/FalkorDB/text-to-cypher/releases/download/v${CLEAN_VERSION}/text-to-cypher-linux-x86_64-musl.tar.gz"
ARM_URL="https://github.com/FalkorDB/text-to-cypher/releases/download/v${CLEAN_VERSION}/text-to-cypher-linux-aarch64-musl.tar.gz"
echo "๐ Debug: Checking asset URLs:"
echo " VERSION: ${VERSION}"
echo " CLEAN_VERSION: ${CLEAN_VERSION}"
echo " X86_URL: ${X86_URL}"
echo " ARM_URL: ${ARM_URL}"
echo "๐ Testing x86_64 asset..."
X86_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$X86_URL")
echo " Raw curl exit code: $?"
echo " HTTP status: $X86_STATUS"
echo "๐ Testing aarch64 asset..."
ARM_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$ARM_URL")
echo " Raw curl exit code: $?"
echo " HTTP status: $ARM_STATUS"
# Test with verbose curl for debugging
echo "๐ Detailed x86_64 asset check:"
curl -v -s -o /dev/null -w "Final HTTP code: %{http_code}\n" "$X86_URL" 2>&1 | head -10
echo "๐ Detailed aarch64 asset check:"
curl -v -s -o /dev/null -w "Final HTTP code: %{http_code}\n" "$ARM_URL" 2>&1 | head -10
# Accept both 200 (direct) and 302 (redirect) as success
X86_OK=false
ARM_OK=false
echo "๐งช Evaluating x86_64 status (received: '$X86_STATUS'):"
if [ "$X86_STATUS" = "200" ]; then
X86_OK=true
echo " โ
Status 200 - Direct download available"
elif [ "$X86_STATUS" = "302" ]; then
X86_OK=true
echo " โ
Status 302 - Redirect (likely to S3/CDN)"
else
echo " โ Unexpected status: '$X86_STATUS'"
echo " Expected: '200' or '302'"
fi
echo "๐งช Evaluating aarch64 status (received: '$ARM_STATUS'):"
if [ "$ARM_STATUS" = "200" ]; then
ARM_OK=true
echo " โ
Status 200 - Direct download available"
elif [ "$ARM_STATUS" = "302" ]; then
ARM_OK=true
echo " โ
Status 302 - Redirect (likely to S3/CDN)"
else
echo " โ Unexpected status: '$ARM_STATUS'"
echo " Expected: '200' or '302'"
fi
echo "๐ Asset availability summary:"
echo " x86_64: $X86_OK (status: $X86_STATUS)"
echo " aarch64: $ARM_OK (status: $ARM_STATUS)"
# Both assets must be available
echo "๐ฏ Decision logic evaluation:"
echo " X86_OK='$X86_OK' (type: $(echo "$X86_OK" | wc -c) chars)"
echo " ARM_OK='$ARM_OK' (type: $(echo "$ARM_OK" | wc -c) chars)"
echo " String comparison test:"
echo " X86_OK == 'true': $([ "$X86_OK" = "true" ] && echo "YES" || echo "NO")"
echo " ARM_OK == 'true': $([ "$ARM_OK" = "true" ] && echo "YES" || echo "NO")"
echo " Combined condition: $([ "$X86_OK" = "true" ] && [ "$ARM_OK" = "true" ] && echo "BOTH_TRUE" || echo "NOT_BOTH_TRUE")"
if [ "$X86_OK" = "true" ] && [ "$ARM_OK" = "true" ]; then
echo "๐ All release assets are available!"
break
else
echo "โณ Waiting for assets to become available..."
echo " Missing: $([ "$X86_OK" != "true" ] && echo "x86_64 " || echo "")$([ "$ARM_OK" != "true" ] && echo "aarch64" || echo "")"
fi
# Provide helpful status updates
if [ $((ATTEMPT % 5)) -eq 0 ]; then
ELAPSED_MINUTES=$((ATTEMPT * 30 / 60))
echo "๐ Progress update after $ELAPSED_MINUTES minutes:"
echo " - x86_64 build: $([ "$X86_OK" = "true" ] && echo "โ
Complete" || echo "๐ In progress")"
echo " - aarch64 build: $([ "$ARM_OK" = "true" ] && echo "โ
Complete" || echo "๐ In progress")"
echo " - Note: ARM64 builds typically take longer than x86_64"
fi
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "โ Timeout after $MAX_ATTEMPTS attempts (30 minutes)"
echo "Final asset status:"
echo " - x86_64: $X86_STATUS ($([ "$X86_OK" = "true" ] && echo "โ
Available" || echo "โ Missing"))"
echo " - aarch64: $ARM_STATUS ($([ "$ARM_OK" = "true" ] && echo "โ
Available" || echo "โ Missing"))"
echo ""
echo "Possible causes:"
echo "1. Cross-platform build taking longer than 30 minutes"
echo "2. Build workflow failed for one or both architectures"
echo "3. GitHub release asset upload issues"
echo ""
echo "Next steps:"
echo "1. Check build workflow: https://github.com/FalkorDB/text-to-cypher/actions"
echo "2. Check release assets: https://github.com/FalkorDB/text-to-cypher/releases/tag/v$CLEAN_VERSION"
echo "3. Retry this workflow manually if builds completed"
exit 1
fi
echo "โณ Waiting 30 seconds... ($(($ATTEMPT * 30 / 60)) min elapsed, up to 30 min total)"
sleep 30
ATTEMPT=$((ATTEMPT + 1))
done
- name: Build and push Docker images
run: |
echo "๐ณ Starting Docker image build process..."
# Check if docker login was successful
LOGIN_SUCCESS="${{ steps.docker-login.outcome }}"
echo "๐ Docker login status: $LOGIN_SUCCESS"
if [ "$LOGIN_SUCCESS" != "success" ]; then
echo "โ Docker login failed; release images cannot be pushed"
exit 1
fi
# Build locally first (single platform only - multi-platform can't load to local daemon)
echo "๐ง Building image locally for current platform (linux/amd64)..."
./docker-build.sh \
--version "${{ steps.extract_version.outputs.release_tag }}" \
--skills-ref "${{ env.CYPHER_SKILLS_REF }}" \
--platforms "linux/amd64" \
--image-name "text-to-cypher"
echo "๐ณ Building and pushing multi-platform Docker image..."
./docker-build.sh \
--version "${{ steps.extract_version.outputs.release_tag }}" \
--skills-ref "${{ env.CYPHER_SKILLS_REF }}" \
--platforms "linux/amd64,linux/arm64" \
--image-name "text-to-cypher" \
--registry "${OWNER_LOWER}" \
--push
echo "๐ Build Summary:"
echo " - Local Build (amd64): โ
Success"
echo " - Docker Hub (${OWNER_LOWER}, multi-platform): โ
Success"
echo " - Cypher skills ref: ${{ env.CYPHER_SKILLS_REF }}"
echo "โ
Docker build process completed successfully"
verify-images:
runs-on: ubuntu-latest
needs: docker-build
if: needs.docker-build.outputs.release_tag && needs.docker-build.outputs.release_tag != '' && needs.docker-build.outputs.release_tag != 'null' && (success() || failure())
strategy:
matrix:
platform: [linux/amd64, linux/arm64]
steps:
- name: Normalize Docker owner
run: echo "OWNER_LOWER=$(printf '%s' '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Extract release version
id: extract_version
run: |
# Extract version from different trigger types
if [ "${{ github.event_name }}" = "release" ]; then
RELEASE_TAG="${{ github.event.release.tag_name }}"
elif [ "${{ github.event_name }}" = "workflow_run" ]; then
# For workflow_run, extract from the head branch if it's a tag
if [[ "${{ github.event.workflow_run.head_branch }}" =~ ^v[0-9] ]]; then
RELEASE_TAG="${{ github.event.workflow_run.head_branch }}"
else
echo "Not a tag-triggered workflow run, skipping"
exit 0
fi
elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref_type }}" = "tag" ]; then
RELEASE_TAG="${{ github.ref_name }}"
else
RELEASE_TAG="${{ github.event.inputs.version }}"
fi
echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a
- name: Log in to Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Verify image exists and runs
run: |
# Try to verify images from different sources
PLATFORM="${{ matrix.platform }}"
REGISTRY_IMAGE="${REGISTRY}/${OWNER_LOWER}/text-to-cypher:${{ steps.extract_version.outputs.release_tag }}"
DOCKERHUB_IMAGE="${OWNER_LOWER}/text-to-cypher:${{ steps.extract_version.outputs.release_tag }}"
echo "Verifying images for platform: ${PLATFORM}"
# Try the explicit Docker Hub registry path first
echo "๐ Trying to pull from Docker Hub (${REGISTRY}/${OWNER_LOWER})..."
set +e
docker pull --platform="${PLATFORM}" "${REGISTRY_IMAGE}"
REGISTRY_PULL_EXIT=$?
set -e
if [ $REGISTRY_PULL_EXIT -eq 0 ]; then
echo "โ
Successfully pulled from Docker Hub: ${REGISTRY_IMAGE}"
TEST_IMAGE="${REGISTRY_IMAGE}"
else
echo "โ ๏ธ Failed to pull explicit Docker Hub path, trying unqualified alias..."
set +e
docker pull --platform="${PLATFORM}" "${DOCKERHUB_IMAGE}"
DOCKERHUB_PULL_EXIT=$?
set -e
if [ $DOCKERHUB_PULL_EXIT -eq 0 ]; then
echo "โ
Successfully pulled from Docker Hub: ${DOCKERHUB_IMAGE}"
TEST_IMAGE="${DOCKERHUB_IMAGE}"
else
echo "โ ๏ธ Could not pull from either registry, checking local images..."
# Check if image exists locally
if docker image inspect "${DOCKERHUB_IMAGE}" >/dev/null 2>&1; then
echo "โ
Found local image: ${DOCKERHUB_IMAGE}"
TEST_IMAGE="${DOCKERHUB_IMAGE}"
elif docker image inspect "${REGISTRY_IMAGE}" >/dev/null 2>&1; then
echo "โ
Found local image: ${REGISTRY_IMAGE}"
TEST_IMAGE="${REGISTRY_IMAGE}"
else
echo "โ No images found locally or in registries"
echo "This might be due to push failures in the previous step"
exit 1
fi
fi
fi
# Test the image we found
echo "๐งช Testing image: ${TEST_IMAGE}"
docker run --rm \
--platform="${PLATFORM}" \
--entrypoint /bin/sh \
"${TEST_IMAGE}" \
-c 'test -x /app/text-to-cypher && supervisord --version && id appuser && test -d /app/skills && SKILL_COUNT=$(find /app/skills -name skill.md | wc -l) && echo "Found ${SKILL_COUNT} skill(s) in /app/skills" && test "${SKILL_COUNT}" -gt 0'
echo "โ
Image verification completed successfully for ${PLATFORM}"
update-deployment:
runs-on: ubuntu-latest
needs: [docker-build, verify-images]
if: ${{ (!github.event.release.prerelease || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run') && needs.docker-build.outputs.release_tag && needs.docker-build.outputs.release_tag != '' && needs.docker-build.outputs.release_tag != 'null' && success() }}
steps:
- name: Normalize Docker owner
run: echo "OWNER_LOWER=$(printf '%s' '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Extract release version
id: extract_version
run: |
# Extract version from different trigger types
if [ "${{ github.event_name }}" = "release" ]; then
RELEASE_TAG="${{ github.event.release.tag_name }}"
elif [ "${{ github.event_name }}" = "workflow_run" ]; then
# For workflow_run, extract from the head branch if it's a tag
if [[ "${{ github.event.workflow_run.head_branch }}" =~ ^v[0-9] ]]; then
RELEASE_TAG="${{ github.event.workflow_run.head_branch }}"
else
echo "Not a tag-triggered workflow run, skipping"
exit 0
fi
elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref_type }}" = "tag" ]; then
RELEASE_TAG="${{ github.ref_name }}"
else
RELEASE_TAG="${{ github.event.inputs.version }}"
fi
echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
- name: Update deployment examples
run: |
echo "๐ Docker images built and verified for release: ${{ steps.extract_version.outputs.release_tag }}"
echo ""
echo "๐ฆ Available images:"
echo " - ${REGISTRY}/${OWNER_LOWER}/text-to-cypher:${{ steps.extract_version.outputs.release_tag }}"
echo " - ${REGISTRY}/${OWNER_LOWER}/text-to-cypher:latest"
echo " - ${OWNER_LOWER}/text-to-cypher:${{ steps.extract_version.outputs.release_tag }}"
echo " - ${OWNER_LOWER}/text-to-cypher:latest"
echo ""
echo "๐ To run the latest release:"
echo " docker run -p 6379:6379 -p 3000:3000 -p 8080:8080 -p 3001:3001 \\"
echo " -e DEFAULT_MODEL=gpt-4o-mini -e DEFAULT_KEY=your-key \\"
echo " ${REGISTRY}/${OWNER_LOWER}/text-to-cypher:${{ steps.extract_version.outputs.release_tag }}"
echo ""
echo "๐ Available platforms: linux/amd64, linux/arm64"
- name: Create deployment summary
run: |
cat >> $GITHUB_STEP_SUMMARY << EOF
# ๐ณ Docker Release Build Complete
## Release Information
- **Version**: \`${{ steps.extract_version.outputs.release_tag }}\`
- **Repository**: \`${{ github.repository }}\`
- **Platforms**: linux/amd64, linux/arm64
## Available Images
- \`${REGISTRY}/${OWNER_LOWER}/text-to-cypher:${{ steps.extract_version.outputs.release_tag }}\`
- \`${REGISTRY}/${OWNER_LOWER}/text-to-cypher:latest\`
- \`${OWNER_LOWER}/text-to-cypher:${{ steps.extract_version.outputs.release_tag }}\`
- \`${OWNER_LOWER}/text-to-cypher:latest\`
## Quick Start
\`\`\`bash
docker run -p 6379:6379 -p 3000:3000 -p 8080:8080 -p 3001:3001 \\
-e DEFAULT_MODEL=gpt-4o-mini -e DEFAULT_KEY=your-key \\
${REGISTRY}/${OWNER_LOWER}/text-to-cypher:${{ steps.extract_version.outputs.release_tag }}
\`\`\`
## Services
- **Port 6379**: FalkorDB (Redis protocol)
- **Port 3000**: FalkorDB web interface
- **Port 8080**: text-to-cypher HTTP API
- **Port 3001**: text-to-cypher MCP server
EOF