Skip to content

Deploy

Deploy #154

Workflow file for this run

# 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"