fix: Add explicit linux/amd64 platform to all Docker builds #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Deploy to Cloud | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'apps/**' | |
| - 'infrastructure/helm/**' | |
| - '.github/workflows/deploy.yml' | |
| workflow_dispatch: # Manual trigger (builds all) | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_PREFIX: ${{ github.repository_owner }}/taskflow | |
| jobs: | |
| # ============================================================================= | |
| # Detect which services changed | |
| # ============================================================================= | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| api: ${{ steps.filter.outputs.api }} | |
| sso: ${{ steps.filter.outputs.sso }} | |
| mcp: ${{ steps.filter.outputs.mcp }} | |
| notification: ${{ steps.filter.outputs.notification }} | |
| web: ${{ steps.filter.outputs.web }} | |
| helm: ${{ steps.filter.outputs.helm }} | |
| any_service: ${{ steps.filter.outputs.api == 'true' || steps.filter.outputs.sso == 'true' || steps.filter.outputs.mcp == 'true' || steps.filter.outputs.notification == 'true' || steps.filter.outputs.web == 'true' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Detect changes | |
| uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| api: | |
| - 'apps/api/**' | |
| sso: | |
| - 'apps/sso/**' | |
| mcp: | |
| - 'apps/mcp-server/**' | |
| notification: | |
| - 'apps/notification-service/**' | |
| web: | |
| - 'apps/web/**' | |
| helm: | |
| - 'infrastructure/helm/**' | |
| # ============================================================================= | |
| # Build Docker images (only changed services) | |
| # ============================================================================= | |
| build-api: | |
| needs: changes | |
| if: needs.changes.outputs.api == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| 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_PREFIX }}/api | |
| tags: | | |
| type=sha,prefix= | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: apps/api | |
| file: apps/api/Dockerfile | |
| push: true | |
| platforms: linux/amd64 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-sso: | |
| needs: changes | |
| if: needs.changes.outputs.sso == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| 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_PREFIX }}/sso | |
| tags: | | |
| type=sha,prefix= | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: apps/sso | |
| file: apps/sso/Dockerfile | |
| push: true | |
| platforms: linux/amd64 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-args: | | |
| NEXT_PUBLIC_BETTER_AUTH_URL=https://sso.${{ vars.DOMAIN }} | |
| NEXT_PUBLIC_CONTINUE_URL=https://${{ vars.DOMAIN }} | |
| NEXT_PUBLIC_APP_NAME=Taskflow SSO | |
| build-mcp: | |
| needs: changes | |
| if: needs.changes.outputs.mcp == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| 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_PREFIX }}/mcp | |
| tags: | | |
| type=sha,prefix= | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: apps/mcp-server | |
| file: apps/mcp-server/Dockerfile | |
| push: true | |
| platforms: linux/amd64 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-notification: | |
| needs: changes | |
| if: needs.changes.outputs.notification == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| 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_PREFIX }}/notification | |
| tags: | | |
| type=sha,prefix= | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: apps/notification-service | |
| file: apps/notification-service/Dockerfile | |
| push: true | |
| platforms: linux/amd64 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-web: | |
| needs: changes | |
| if: needs.changes.outputs.web == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| 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_PREFIX }}/web | |
| tags: | | |
| type=sha,prefix= | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: apps/web | |
| file: apps/web/Dockerfile | |
| push: true | |
| platforms: linux/amd64 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-args: | | |
| NEXT_PUBLIC_SSO_URL=https://sso.${{ vars.DOMAIN }} | |
| NEXT_PUBLIC_API_URL=https://api.${{ vars.DOMAIN }} | |
| NEXT_PUBLIC_APP_URL=https://${{ vars.DOMAIN }} | |
| NEXT_PUBLIC_OAUTH_REDIRECT_URI=https://${{ vars.DOMAIN }}/api/auth/callback | |
| NEXT_PUBLIC_CHATKIT_DOMAIN_KEY=${{ secrets.CHATKIT_DOMAIN_KEY }} | |
| # ============================================================================= | |
| # Deploy to Kubernetes cluster | |
| # ============================================================================= | |
| deploy: | |
| needs: [changes, build-api, build-sso, build-mcp, build-notification, build-web] | |
| # Run if any build ran OR if only helm changed | |
| if: | | |
| always() && | |
| (needs.changes.outputs.any_service == 'true' || needs.changes.outputs.helm == 'true' || github.event_name == 'workflow_dispatch') && | |
| !contains(needs.*.result, 'failure') | |
| runs-on: ubuntu-latest | |
| environment: production | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up kubectl | |
| uses: azure/setup-kubectl@v3 | |
| with: | |
| version: 'v1.28.0' | |
| - name: Set up Helm | |
| uses: azure/setup-helm@v3 | |
| with: | |
| version: 'v3.13.0' | |
| # For Azure AKS | |
| - name: Azure Login | |
| if: ${{ vars.CLOUD_PROVIDER == 'azure' }} | |
| uses: azure/login@v1 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| - name: Get AKS credentials | |
| if: ${{ vars.CLOUD_PROVIDER == 'azure' }} | |
| run: | | |
| az aks get-credentials \ | |
| --resource-group ${{ vars.AZURE_RESOURCE_GROUP }} \ | |
| --name ${{ vars.AZURE_CLUSTER_NAME }} | |
| # For GKE | |
| - name: Authenticate to GKE | |
| if: ${{ vars.CLOUD_PROVIDER == 'gke' }} | |
| uses: google-github-actions/auth@v1 | |
| with: | |
| credentials_json: ${{ secrets.GCP_CREDENTIALS }} | |
| - name: Get GKE credentials | |
| if: ${{ vars.CLOUD_PROVIDER == 'gke' }} | |
| uses: google-github-actions/get-gke-credentials@v1 | |
| with: | |
| cluster_name: ${{ vars.GKE_CLUSTER_NAME }} | |
| location: ${{ vars.GKE_CLUSTER_ZONE }} | |
| # For any provider with kubeconfig | |
| - name: Set kubeconfig | |
| if: ${{ vars.CLOUD_PROVIDER == 'kubeconfig' }} | |
| run: | | |
| mkdir -p ~/.kube | |
| echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config | |
| chmod 600 ~/.kube/config | |
| - name: Add Dapr Helm repo | |
| run: | | |
| helm repo add dapr https://dapr.github.io/helm-charts/ | |
| helm repo update | |
| - name: Install/Upgrade Dapr | |
| run: | | |
| helm upgrade --install dapr dapr/dapr \ | |
| --version=1.15 \ | |
| --namespace dapr-system \ | |
| --create-namespace \ | |
| --set dapr_scheduler.cluster.storageSize=4Gi \ | |
| --wait | |
| - name: Create namespace | |
| run: kubectl create namespace taskflow --dry-run=client -o yaml | kubectl apply -f - | |
| - name: Create GHCR pull secret | |
| run: | | |
| kubectl create secret docker-registry ghcr-secret \ | |
| --namespace taskflow \ | |
| --docker-server=ghcr.io \ | |
| --docker-username=${{ github.actor }} \ | |
| --docker-password=${{ secrets.GITHUB_TOKEN }} \ | |
| --dry-run=client -o yaml | kubectl apply -f - | |
| - name: Deploy with Helm | |
| run: | | |
| # Determine ingress class based on what's installed | |
| INGRESS_CLASS="${{ vars.INGRESS_CLASS }}" | |
| if [ -z "$INGRESS_CLASS" ]; then | |
| INGRESS_CLASS="traefik" # Default to traefik | |
| fi | |
| # Create temporary values file for comma-containing values (avoids Helm --set parsing issues) | |
| cat > /tmp/helm-overrides.yaml << 'ENDOFVALUES' | |
| sso: | |
| env: | |
| ALLOWED_ORIGINS: "https://${{ vars.DOMAIN }},https://sso.${{ vars.DOMAIN }},https://api.${{ vars.DOMAIN }},https://mcp.${{ vars.DOMAIN }}" | |
| api: | |
| env: | |
| CORS_ORIGINS: "https://${{ vars.DOMAIN }},https://sso.${{ vars.DOMAIN }},https://api.${{ vars.DOMAIN }},https://mcp.${{ vars.DOMAIN }}" | |
| ENDOFVALUES | |
| helm upgrade --install taskflow ./infrastructure/helm/taskflow \ | |
| --namespace taskflow \ | |
| --values infrastructure/helm/taskflow/values-cloud.yaml \ | |
| --values /tmp/helm-overrides.yaml \ | |
| --set global.imagePullSecrets[0].name=ghcr-secret \ | |
| --set "sso.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/sso" \ | |
| --set "sso.image.tag=latest" \ | |
| --set "api.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/api" \ | |
| --set "api.image.tag=latest" \ | |
| --set "web.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/web" \ | |
| --set "web.image.tag=latest" \ | |
| --set "mcpServer.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/mcp" \ | |
| --set "mcpServer.image.tag=latest" \ | |
| --set "notificationService.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/notification" \ | |
| --set "notificationService.image.tag=latest" \ | |
| --set managedServices.neon.enabled=true \ | |
| --set "managedServices.neon.ssoDatabase=${{ secrets.NEON_SSO_DATABASE_URL }}" \ | |
| --set "managedServices.neon.apiDatabase=${{ secrets.NEON_API_DATABASE_URL }}" \ | |
| --set "managedServices.neon.chatkitDatabase=${{ secrets.NEON_CHATKIT_DATABASE_URL }}" \ | |
| --set "managedServices.neon.notificationDatabase=${{ secrets.NEON_NOTIFICATION_DATABASE_URL }}" \ | |
| --set managedServices.upstash.enabled=true \ | |
| --set "managedServices.upstash.host=${{ secrets.UPSTASH_REDIS_HOST }}" \ | |
| --set "managedServices.upstash.password=${{ secrets.UPSTASH_REDIS_PASSWORD }}" \ | |
| --set "managedServices.upstash.restUrl=${{ secrets.REDIS_URL }}" \ | |
| --set "managedServices.upstash.restToken=${{ secrets.REDIS_TOKEN }}" \ | |
| --set "sso.env.BETTER_AUTH_SECRET=${{ secrets.BETTER_AUTH_SECRET }}" \ | |
| --set "sso.env.BETTER_AUTH_URL=https://sso.${{ vars.DOMAIN }}" \ | |
| --set "sso.smtp.user=${{ secrets.SMTP_USER }}" \ | |
| --set "sso.smtp.password=${{ secrets.SMTP_PASSWORD }}" \ | |
| --set "api.openai.apiKey=${{ secrets.OPENAI_API_KEY }}" \ | |
| --set notificationService.enabled=true \ | |
| --set dapr.enabled=true \ | |
| --set "dapr.pubsub.redisHost=${{ secrets.UPSTASH_REDIS_HOST }}" \ | |
| --set "dapr.pubsub.redisPassword=${{ secrets.UPSTASH_REDIS_PASSWORD }}" \ | |
| --set "global.domain=${{ vars.DOMAIN }}" \ | |
| --set "sso.ingress.className=$INGRESS_CLASS" \ | |
| --set "sso.ingress.host=sso.${{ vars.DOMAIN }}" \ | |
| --set "api.ingress.className=$INGRESS_CLASS" \ | |
| --set "api.ingress.host=api.${{ vars.DOMAIN }}" \ | |
| --set "mcpServer.ingress.enabled=true" \ | |
| --set "mcpServer.ingress.className=$INGRESS_CLASS" \ | |
| --set "mcpServer.ingress.host=mcp.${{ vars.DOMAIN }}" \ | |
| --set "mcpServer.env.TASKFLOW_SSO_PUBLIC_URL=https://sso.${{ vars.DOMAIN }}" \ | |
| --set "web.ingress.className=$INGRESS_CLASS" \ | |
| --set "web.ingress.host=${{ vars.DOMAIN }}" \ | |
| --set "ingress-nginx.enabled=false" \ | |
| --wait \ | |
| --timeout 10m | |
| - name: Verify deployment | |
| run: | | |
| echo "Checking pod status..." | |
| kubectl get pods -n taskflow | |
| echo "" | |
| echo "Checking services..." | |
| kubectl get svc -n taskflow | |
| echo "" | |
| echo "Checking ingress..." | |
| kubectl get ingress -n taskflow 2>/dev/null || echo "No ingress configured" | |
| - name: Post deployment URLs | |
| run: | | |
| echo "## Deployment Complete! :rocket:" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Services Built" >> $GITHUB_STEP_SUMMARY | |
| echo "| Service | Built |" >> $GITHUB_STEP_SUMMARY | |
| echo "|---------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| API | ${{ needs.changes.outputs.api == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| SSO | ${{ needs.changes.outputs.sso == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| MCP | ${{ needs.changes.outputs.mcp == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Notification | ${{ needs.changes.outputs.notification == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Web | ${{ needs.changes.outputs.web == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### URLs" >> $GITHUB_STEP_SUMMARY | |
| echo "| Service | URL |" >> $GITHUB_STEP_SUMMARY | |
| echo "|---------|-----|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Web Dashboard | https://${{ vars.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| SSO Platform | https://sso.${{ vars.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| API | https://api.${{ vars.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| MCP Server | https://mcp.${{ vars.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY |