Deploy #154
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
| # Deploy Pipeline - Runs on merge to main branch | |
| # Builds and deploys frontend to Vercel/DO and backend as Docker container | |
| name: Deploy | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| description: 'Deployment environment' | |
| required: true | |
| default: 'production' | |
| type: choice | |
| options: | |
| - production | |
| - staging | |
| concurrency: | |
| group: deploy-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| PYTHON_VERSION: '3.11' | |
| NODE_VERSION: '20' | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }}/backend | |
| jobs: | |
| # ==================== BUILD FRONTEND ==================== | |
| build-frontend: | |
| name: Build Frontend | |
| runs-on: ubuntu-latest | |
| outputs: | |
| has_frontend: ${{ steps.check.outputs.has_frontend }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Check for package.json | |
| id: check | |
| run: | | |
| if [ -f "frontend/package.json" ]; then | |
| echo "has_frontend=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_frontend=false" >> $GITHUB_OUTPUT | |
| echo "::notice::No frontend package.json found - skipping frontend build" | |
| fi | |
| - name: Set up Node.js | |
| if: steps.check.outputs.has_frontend == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install Dependencies | |
| if: steps.check.outputs.has_frontend == 'true' | |
| working-directory: frontend | |
| run: npm ci | |
| - name: Build Production Bundle | |
| if: steps.check.outputs.has_frontend == 'true' | |
| working-directory: frontend | |
| run: npm run build | |
| env: | |
| NODE_ENV: production | |
| NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }} | |
| - name: Upload Build Artifacts | |
| if: steps.check.outputs.has_frontend == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: frontend-build | |
| path: frontend/.next/ | |
| retention-days: 7 | |
| # ==================== DEPLOY FRONTEND TO VERCEL ==================== | |
| deploy-frontend: | |
| name: Deploy Frontend (Vercel) | |
| runs-on: ubuntu-latest | |
| needs: build-frontend | |
| if: needs.build-frontend.outputs.has_frontend == 'true' && github.event_name != 'workflow_dispatch' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Deploy to Vercel | |
| uses: amondnet/vercel-action@v25 | |
| with: | |
| vercel-token: ${{ secrets.VERCEL_TOKEN }} | |
| vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} | |
| vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} | |
| vercel-args: '--prod' | |
| working-directory: frontend | |
| # ==================== BUILD BACKEND DOCKER IMAGE ==================== | |
| build-backend: | |
| name: Build Backend Docker Image | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| image_tag: ${{ steps.meta.outputs.tags }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to Container Registry | |
| 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 }} | |
| tags: | | |
| type=sha,prefix= | |
| type=ref,event=branch | |
| type=semver,pattern={{version}} | |
| - name: Build and Push Docker Image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: ./backend | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| file: ./backend/Dockerfile | |
| continue-on-error: true | |
| # ==================== DEPLOY BACKEND TO DIGITALOCEAN ==================== | |
| deploy-backend: | |
| name: Deploy Backend (DigitalOcean) | |
| runs-on: ubuntu-latest | |
| needs: build-backend | |
| if: github.event_name != 'workflow_dispatch' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install doctl | |
| uses: digitalocean/action-doctl@v2 | |
| with: | |
| token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} | |
| - name: Save DigitalOcean kubeconfig | |
| run: doctl kubernetes cluster kubeconfig save ${{ secrets.DIGITALOCEAN_CLUSTER_NAME }} | |
| - name: Deploy to Kubernetes | |
| run: | | |
| # Update deployment image | |
| kubectl set image deployment/solfoundry-backend \ | |
| backend=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ | |
| --namespace production | |
| # Wait for rollout | |
| kubectl rollout status deployment/solfoundry-backend \ | |
| --namespace production \ | |
| --timeout=300s | |
| continue-on-error: true | |
| # ==================== DATABASE MIGRATIONS ==================== | |
| migrate-database: | |
| name: Run Database Migrations | |
| runs-on: ubuntu-latest | |
| needs: deploy-backend | |
| if: github.event_name != 'workflow_dispatch' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install Dependencies | |
| working-directory: backend | |
| run: | | |
| pip install -r requirements.txt | |
| pip install alembic | |
| - name: Run Migrations | |
| working-directory: backend | |
| run: alembic upgrade head | |
| env: | |
| DATABASE_URL: ${{ secrets.DATABASE_URL }} | |
| continue-on-error: true | |
| # ==================== DEPLOYMENT SUMMARY ==================== | |
| deploy-status: | |
| name: Deployment Status | |
| runs-on: ubuntu-latest | |
| needs: [build-frontend, deploy-frontend, build-backend, deploy-backend, migrate-database] | |
| if: always() | |
| steps: | |
| - name: Generate Deployment Summary | |
| run: | | |
| echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Environment:** Production" >> $GITHUB_STEP_SUMMARY | |
| echo "**Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Component | Status |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-----------|--------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Frontend Build | ${{ needs.build-frontend.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Frontend Deploy | ${{ needs.deploy-frontend.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Backend Build | ${{ needs.build-backend.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Backend Deploy | ${{ needs.deploy-backend.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Database Migration | ${{ needs.migrate-database.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Links" >> $GITHUB_STEP_SUMMARY | |
| echo "- [Production Site](https://solfoundry.org)" >> $GITHUB_STEP_SUMMARY | |
| echo "- [API Health](https://api.solfoundry.org/health)" >> $GITHUB_STEP_SUMMARY | |
| - name: Notify on Success | |
| if: success() | |
| run: echo "Deployment completed successfully!" | |
| - name: Notify on Failure | |
| if: failure() | |
| run: echo "Deployment failed - check the logs above for details" |