name: Deploy on: push: branches: [main] workflow_dispatch: inputs: environment: description: 'Environment to deploy to' required: true default: 'production' type: choice options: - production - staging env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: # ============================================================ # Build & Push Docker Images # ============================================================ build-and-push: name: Build & Push Images runs-on: ubuntu-latest permissions: contents: read packages: write outputs: backend-image: ${{ steps.meta-backend.outputs.tags }} frontend-image: ${{ steps.meta-frontend.outputs.tags }} steps: - name: Checkout code 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 (backend) id: meta-backend uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-backend tags: | type=sha,prefix= type=raw,value=latest,enable={{is_default_branch}} - name: Extract metadata (frontend) id: meta-frontend uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend tags: | type=sha,prefix= type=raw,value=latest,enable={{is_default_branch}} - name: Build and push backend uses: docker/build-push-action@v5 with: context: ./backend push: true tags: ${{ steps.meta-backend.outputs.tags }} labels: ${{ steps.meta-backend.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - name: Build and push frontend uses: docker/build-push-action@v5 with: context: ./frontend push: true tags: ${{ steps.meta-frontend.outputs.tags }} labels: ${{ steps.meta-frontend.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max # ============================================================ # Deploy to Server # ============================================================ deploy: name: Deploy to Server runs-on: ubuntu-latest needs: build-and-push environment: name: ${{ github.event.inputs.environment || 'production' }} url: ${{ vars.SITE_URL }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Deploy via SSH uses: appleboy/ssh-action@v1.0.3 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: ${{ secrets.SSH_PORT || 22 }} script: | cd ${{ vars.DEPLOY_PATH || '/opt/pounce' }} # Pull latest changes git pull origin main # Pull new images docker compose pull # Restart services with zero downtime docker compose up -d --remove-orphans # Run database migrations docker compose exec -T backend alembic upgrade head || true # Cleanup old images docker image prune -f # Health check sleep 10 curl -f http://localhost:8000/health || exit 1 curl -f http://localhost:3000 || exit 1 echo "Deployment completed successfully!" # ============================================================ # Notify on completion # ============================================================ notify: name: Notify runs-on: ubuntu-latest needs: [build-and-push, deploy] if: always() steps: - name: Send notification run: | if [ "${{ needs.deploy.result }}" == "success" ]; then echo "✅ Deployment successful!" else echo "❌ Deployment failed!" exit 1 fi