name: Release + Docker Images (multi-arch) on: push: branches: - main paths: - 'pyproject.toml' workflow_dispatch: jobs: check-version: runs-on: ubuntu-latest outputs: skip_release: ${{ steps.version.outputs.skip_release }} version: ${{ steps.version.outputs.version }} docker_version: ${{ steps.version.outputs.docker_version }} is_prerelease: ${{ steps.version.outputs.is_prerelease }} steps: - name: Checkout uses: actions/checkout@v4 - name: Extract version from pyproject.toml id: version run: | VERSION=$(grep '^version = ' pyproject.toml | cut -d '"' -f 2) echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Version: $VERSION" # Normalize version per PEP 440 for Docker tags # e.g., "0.1.53-rc2" -> "0.1.53rc2" to match Python's importlib.metadata DOCKER_VERSION=$(echo "$VERSION" | sed -E 's/-?(rc|alpha|beta|dev|post)/\1/g') echo "docker_version=$DOCKER_VERSION" >> $GITHUB_OUTPUT echo "Docker Version: $DOCKER_VERSION" # Check if tag already exists if git rev-parse "v$VERSION" >/dev/null 2>&1; then echo "Tag v$VERSION already exists, skipping release" echo "skip_release=true" >> $GITHUB_OUTPUT exit 0 fi echo "skip_release=false" >> $GITHUB_OUTPUT # Check if version is numeric (e.g., 0.1.16) vs prerelease (e.g., 0.1.16-rc1) if [[ "$VERSION" =~ ^[0-9.-]+$ ]]; then echo "is_prerelease=false" >> $GITHUB_OUTPUT echo "Release type: Production" else echo "is_prerelease=true" >> $GITHUB_OUTPUT echo "Release type: Prerelease" fi build: needs: check-version if: needs.check-version.outputs.skip_release != 'true' strategy: fail-fast: false matrix: include: # backend - image: backend file: ./Dockerfile.backend tag: langflowai/openrag-backend platform: linux/amd64 arch: amd64 runs-on: ubuntu-latest-16-cores - image: backend file: ./Dockerfile.backend tag: langflowai/openrag-backend platform: linux/arm64 arch: arm64 runs-on: [self-hosted, Linux, ARM64, langflow-ai-arm64-40gb] # frontend - image: frontend file: ./Dockerfile.frontend tag: langflowai/openrag-frontend platform: linux/amd64 arch: amd64 runs-on: ubuntu-latest-16-cores - image: frontend file: ./Dockerfile.frontend tag: langflowai/openrag-frontend platform: linux/arm64 arch: arm64 runs-on: [self-hosted, Linux, ARM64, langflow-ai-arm64-40gb] # langflow - image: langflow file: ./Dockerfile.langflow tag: langflowai/openrag-langflow platform: linux/amd64 arch: amd64 runs-on: ubuntu-latest-16-cores - image: langflow file: ./Dockerfile.langflow tag: langflowai/openrag-langflow platform: linux/arm64 arch: arm64 runs-on: [self-hosted, Linux, ARM64, langflow-ai-arm64-40gb] # opensearch - image: opensearch file: ./Dockerfile tag: langflowai/openrag-opensearch platform: linux/amd64 arch: amd64 runs-on: ubuntu-latest-16-cores - image: opensearch file: ./Dockerfile tag: langflowai/openrag-opensearch platform: linux/arm64 arch: arm64 runs-on: [self-hosted, Linux, ARM64, langflow-ai-arm64-40gb] runs-on: ${{ matrix.runs-on }} steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push ${{ matrix.image }} (${{ matrix.arch }}) uses: docker/build-push-action@v5 with: context: . file: ${{ matrix.file }} platforms: ${{ matrix.platform }} push: ${{ github.event_name != 'pull_request' }} tags: ${{ matrix.tag }}:${{ needs.check-version.outputs.docker_version }}-${{ matrix.arch }} cache-from: type=gha,scope=${{ matrix.image }}-${{ matrix.arch }} cache-to: type=gha,mode=max,scope=${{ matrix.image }}-${{ matrix.arch }} manifest: needs: [build, check-version] runs-on: ubuntu-latest if: github.event_name != 'pull_request' && needs.check-version.outputs.skip_release != 'true' steps: - name: Checkout uses: actions/checkout@v4 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Create and push multi-arch manifests run: | VERSION=${{ needs.check-version.outputs.docker_version }} # Create versioned tags docker buildx imagetools create -t langflowai/openrag-backend:$VERSION \ langflowai/openrag-backend:$VERSION-amd64 \ langflowai/openrag-backend:$VERSION-arm64 docker buildx imagetools create -t langflowai/openrag-frontend:$VERSION \ langflowai/openrag-frontend:$VERSION-amd64 \ langflowai/openrag-frontend:$VERSION-arm64 docker buildx imagetools create -t langflowai/openrag-langflow:$VERSION \ langflowai/openrag-langflow:$VERSION-amd64 \ langflowai/openrag-langflow:$VERSION-arm64 docker buildx imagetools create -t langflowai/openrag-opensearch:$VERSION \ langflowai/openrag-opensearch:$VERSION-amd64 \ langflowai/openrag-opensearch:$VERSION-arm64 # Only update latest tags if version is numeric if [[ "$VERSION" =~ ^[0-9.-]+$ ]]; then echo "Updating latest tags for production release: $VERSION" docker buildx imagetools create -t langflowai/openrag-backend:latest \ langflowai/openrag-backend:$VERSION-amd64 \ langflowai/openrag-backend:$VERSION-arm64 docker buildx imagetools create -t langflowai/openrag-frontend:latest \ langflowai/openrag-frontend:$VERSION-amd64 \ langflowai/openrag-frontend:$VERSION-arm64 docker buildx imagetools create -t langflowai/openrag-langflow:latest \ langflowai/openrag-langflow:$VERSION-amd64 \ langflowai/openrag-langflow:$VERSION-arm64 docker buildx imagetools create -t langflowai/openrag-opensearch:latest \ langflowai/openrag-opensearch:$VERSION-amd64 \ langflowai/openrag-opensearch:$VERSION-arm64 else echo "Skipping latest tags - version: $VERSION (not numeric)" fi build-python-packages: needs: [manifest, check-version] runs-on: ubuntu-latest if: needs.check-version.outputs.skip_release != 'true' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.13' - name: Install uv uses: astral-sh/setup-uv@v3 - name: Build wheel and source distribution run: | uv build - name: List built artifacts run: | ls -la dist/ echo "Built artifacts:" for file in dist/*; do echo " - $(basename $file) ($(stat -c%s $file | numfmt --to=iec-i)B)" done - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: python-packages path: dist/ retention-days: 30 - name: Create Release uses: softprops/action-gh-release@v2 with: tag_name: v${{ needs.check-version.outputs.version }} name: Release ${{ needs.check-version.outputs.version }} draft: false prerelease: ${{ needs.check-version.outputs.is_prerelease }} generate_release_notes: true files: | dist/*.whl dist/*.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish to PyPI run: | uv publish env: UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }}