name: Docker Build # PR validation and manual builds. Production pushes are handled by release.yml. on: pull_request: branches: [main] workflow_dispatch: inputs: push: description: 'Push images to Docker Hub' required: true type: boolean default: false concurrency: group: docker-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: IMAGE_NAME_MCP: celstnblacc/excalidraw-mcp-sentinel IMAGE_NAME_CANVAS: celstnblacc/excalidraw-mcp-sentinel-canvas jobs: check-changes: name: Check for Docker-related changes runs-on: ubuntu-latest outputs: should_build: ${{ steps.filter.outputs.should_build }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - name: Check changed files id: filter run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "should_build=true" >> "$GITHUB_OUTPUT" exit 0 fi CHANGED=$(git diff --name-only origin/main...HEAD || true) if echo "$CHANGED" | grep -qE '^(Dockerfile|src/|frontend/|package\.json)'; then echo "should_build=true" >> "$GITHUB_OUTPUT" else echo "No Docker-related files changed, skipping builds" echo "should_build=false" >> "$GITHUB_OUTPUT" fi build-mcp: name: Build MCP Server image needs: check-changes if: needs.check-changes.outputs.should_build == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Log in to Docker Hub if: github.event_name == 'workflow_dispatch' && github.event.inputs.push == 'true' uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_NAME_MCP }} tags: | type=ref,event=pr type=sha,prefix=sha- - name: Build MCP Server image uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v5.5.0 with: context: . file: ./Dockerfile push: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.push == 'true' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max build-canvas: name: Build Canvas Server image needs: check-changes if: needs.check-changes.outputs.should_build == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Log in to Docker Hub if: github.event_name == 'workflow_dispatch' && github.event.inputs.push == 'true' uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_NAME_CANVAS }} tags: | type=ref,event=pr type=sha,prefix=sha- - name: Build Canvas Server image uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v5.5.0 with: context: . file: ./Dockerfile.canvas push: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.push == 'true' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max test-images: name: Test Docker images needs: [build-mcp, build-canvas] if: needs.build-mcp.result == 'success' && needs.build-canvas.result == 'success' runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Build and test Canvas image locally run: | docker build -f Dockerfile.canvas -t canvas-test . docker run -d -p 3000:3000 --name test-canvas canvas-test HEALTHY=false for i in $(seq 1 30); do if curl -sf http://localhost:3000/health > /dev/null 2>&1; then echo "Health check passed on attempt $i" HEALTHY=true break fi echo "Waiting for server... ($i/30)" sleep 1 done if [ "$HEALTHY" != "true" ]; then echo "::error::Health check failed after 30 attempts" docker logs test-canvas docker stop test-canvas || true exit 1 fi curl -sf http://localhost:3000/health | jq . docker stop test-canvas docker-status: runs-on: ubuntu-latest continue-on-error: false name: Docker Build Status Check needs: [check-changes, build-mcp, build-canvas, test-images] if: always() permissions: statuses: write steps: - name: Failed id: failed if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') run: | curl -X POST -H "Content-Type: application/json" -H "Authorization: token ${{ github.token }}" \ -d '{ "state" : "failure" , "context" : "github/docker-build-check" , "description" : "Docker build failed", "target_url" : "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" }' \ https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} exit 1 - name: Success if: steps.failed.conclusion == 'skipped' run: | curl -X POST -H "Content-Type: application/json" -H "Authorization: token ${{ github.token }}" \ -d '{ "state" : "success" , "context" : "github/docker-build-check" , "description" : "Docker build passed", "target_url" : "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" }' \ https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }}