name: Deploy on: push: branches: [main] workflow_dispatch: inputs: environment: description: Target environment required: true default: staging type: choice options: - staging - production concurrency: group: deploy-${{ inputs.environment || 'staging' }} cancel-in-progress: false env: REGISTRY: ghcr.io REGISTRY_URL: ghcr.io/${{ github.repository_owner }} jobs: build-api: name: Build API Image 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: Login to GitHub 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_URL }}/goodgo-api tags: | type=sha,prefix= type=ref,event=branch type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - name: Build and push API image uses: docker/build-push-action@v6 with: context: . file: apps/api/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=api cache-to: type=gha,mode=max,scope=api build-web: name: Build Web Image 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: Login to GitHub 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_URL }}/goodgo-web tags: | type=sha,prefix= type=ref,event=branch type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - name: Build and push Web image uses: docker/build-push-action@v6 with: context: . file: apps/web/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=web cache-to: type=gha,mode=max,scope=web build-ai: name: Build AI Services Image 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: Login to GitHub 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_URL }}/goodgo-ai-services tags: | type=sha,prefix= type=ref,event=branch type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - name: Build and push AI Services image uses: docker/build-push-action@v6 with: context: ./libs/ai-services file: libs/ai-services/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=ai cache-to: type=gha,mode=max,scope=ai deploy-staging: name: Deploy to Staging needs: [build-api, build-web, build-ai] if: github.event_name == 'push' || inputs.environment == 'staging' runs-on: ubuntu-latest environment: staging steps: - name: Checkout uses: actions/checkout@v4 - name: Deploy to staging env: DEPLOY_HOST: ${{ secrets.STAGING_HOST }} DEPLOY_USER: ${{ secrets.STAGING_USER }} DEPLOY_KEY: ${{ secrets.STAGING_SSH_KEY }} IMAGE_TAG: ${{ github.sha }} run: | mkdir -p ~/.ssh echo "$DEPLOY_KEY" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null # Copy production compose and deploy scp -i ~/.ssh/deploy_key docker-compose.prod.yml "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/" scp -i ~/.ssh/deploy_key -r monitoring/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/monitoring/" ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << 'DEPLOY_SCRIPT' cd ~/goodgo export IMAGE_TAG="${IMAGE_TAG}" export REGISTRY_URL="${REGISTRY_URL}" # Login to GHCR echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin # Pull new images docker compose -f docker-compose.prod.yml pull api web ai-services # Rolling update — zero downtime docker compose -f docker-compose.prod.yml up -d --no-deps --wait api docker compose -f docker-compose.prod.yml up -d --no-deps --wait web docker compose -f docker-compose.prod.yml up -d --no-deps --wait ai-services # Run database migrations docker compose -f docker-compose.prod.yml exec api npx prisma migrate deploy # Cleanup old images docker image prune -f DEPLOY_SCRIPT - name: Verify staging deployment env: STAGING_URL: ${{ secrets.STAGING_URL }} run: | for i in $(seq 1 10); do if curl -sf "$STAGING_URL/health" > /dev/null 2>&1; then echo "Staging deployment verified successfully" exit 0 fi echo "Waiting for staging to be ready... ($i/10)" sleep 10 done echo "Staging health check failed" exit 1 deploy-production: name: Deploy to Production needs: [build-api, build-web, build-ai] if: inputs.environment == 'production' runs-on: ubuntu-latest environment: production steps: - name: Checkout uses: actions/checkout@v4 - name: Deploy to production env: DEPLOY_HOST: ${{ secrets.PRODUCTION_HOST }} DEPLOY_USER: ${{ secrets.PRODUCTION_USER }} DEPLOY_KEY: ${{ secrets.PRODUCTION_SSH_KEY }} IMAGE_TAG: ${{ github.sha }} run: | mkdir -p ~/.ssh echo "$DEPLOY_KEY" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null scp -i ~/.ssh/deploy_key docker-compose.prod.yml "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/" scp -i ~/.ssh/deploy_key -r monitoring/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/monitoring/" ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << 'DEPLOY_SCRIPT' cd ~/goodgo export IMAGE_TAG="${IMAGE_TAG}" export REGISTRY_URL="${REGISTRY_URL}" echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin docker compose -f docker-compose.prod.yml pull api web ai-services # Rolling update with health checks docker compose -f docker-compose.prod.yml up -d --no-deps --wait api docker compose -f docker-compose.prod.yml up -d --no-deps --wait web docker compose -f docker-compose.prod.yml up -d --no-deps --wait ai-services docker compose -f docker-compose.prod.yml exec api npx prisma migrate deploy docker image prune -f DEPLOY_SCRIPT - name: Verify production deployment env: PRODUCTION_URL: ${{ secrets.PRODUCTION_URL }} run: | for i in $(seq 1 10); do if curl -sf "$PRODUCTION_URL/health" > /dev/null 2>&1; then echo "Production deployment verified successfully" exit 0 fi echo "Waiting for production to be ready... ($i/10)" sleep 10 done echo "Production health check failed" exit 1