Merge pull request #5 from SkySingh04/feature/cd-pipeline #10
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: CD | ||
|
Check failure on line 1 in .github/workflows/cd.yml
|
||
| on: | ||
| push: | ||
| branches: [ main ] | ||
| pull_request_target: | ||
| branches: [ main ] | ||
| workflow_dispatch: | ||
| permissions: | ||
| contents: read | ||
| packages: write | ||
| env: | ||
| REGISTRY: ghcr.io | ||
| jobs: | ||
| build-and-push: | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| backend_image: ${{ steps.tags.outputs.backend }} | ||
| frontend_image: ${{ steps.tags.outputs.frontend }} | ||
| # Only run on PRs originating from this repo to avoid exposing secrets to forks | ||
| if: ${{ github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository }} | ||
| steps: | ||
| - name: Select ref | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "pull_request_target" ]; then | ||
| echo "REF=${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV | ||
| else | ||
| echo "REF=${GITHUB_SHA}" >> $GITHUB_ENV | ||
| fi | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ env.REF }} | ||
| - name: Derive lowercase image repo | ||
| run: | | ||
| echo "IMAGE_REPO=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV | ||
| - name: Log in to GHCR | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| registry: ${{ env.REGISTRY }} | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Set image tags | ||
| id: tags | ||
| run: | | ||
| SHA_TAG=sha-${GITHUB_SHA::7} | ||
| echo "backend=${{ env.REGISTRY }}/${IMAGE_REPO}-backend:${SHA_TAG}" >> $GITHUB_OUTPUT | ||
| echo "backend_latest=${{ env.REGISTRY }}/${IMAGE_REPO}-backend:latest" >> $GITHUB_OUTPUT | ||
| echo "frontend=${{ env.REGISTRY }}/${IMAGE_REPO}-frontend:${SHA_TAG}" >> $GITHUB_OUTPUT | ||
| echo "frontend_latest=${{ env.REGISTRY }}/${IMAGE_REPO}-frontend:latest" >> $GITHUB_OUTPUT | ||
| - name: Build and push backend | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| file: backend/Dockerfile | ||
| push: true | ||
| tags: | | ||
| ${{ steps.tags.outputs.backend }} | ||
| ${{ steps.tags.outputs.backend_latest }} | ||
| - name: Build and push frontend | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: frontend | ||
| file: frontend/Dockerfile | ||
| push: true | ||
| tags: | | ||
| ${{ steps.tags.outputs.frontend }} | ||
| ${{ steps.tags.outputs.frontend_latest }} | ||
| - name: Show image tags | ||
| run: | | ||
| echo "Backend: ${{ steps.tags.outputs.backend }}" | ||
| echo "Frontend: ${{ steps.tags.outputs.frontend }}" | ||
| deploy-compose: | ||
| runs-on: ubuntu-latest | ||
| needs: build-and-push | ||
| if: ${{ secrets.SSH_HOST != '' && secrets.SSH_USER != '' && secrets.SSH_PRIVATE_KEY != '' && (github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository) }} | ||
| environment: production | ||
| concurrency: | ||
| group: prod-deploy | ||
| cancel-in-progress: false | ||
| steps: | ||
| - name: Select ref | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "pull_request_target" ]; then | ||
| echo "REF=${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV | ||
| else | ||
| echo "REF=${GITHUB_SHA}" >> $GITHUB_ENV | ||
| fi | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ env.REF }} | ||
| - name: Upload docker-compose file | ||
| uses: appleboy/scp-action@v0.1.7 | ||
| with: | ||
| host: ${{ secrets.SSH_HOST }} | ||
| username: ${{ secrets.SSH_USER }} | ||
| key: ${{ secrets.SSH_PRIVATE_KEY }} | ||
| source: "docker-compose.prod.yml" | ||
| target: "/opt/edgeflow/docker-compose.yml" | ||
| strip_components: 0 | ||
| - 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 }} | ||
| script: | | ||
| set -euo pipefail | ||
| APP_DIR=/opt/edgeflow | ||
| sudo mkdir -p ${APP_DIR} | ||
| cd ${APP_DIR} | ||
| # Write .env with image tags | ||
| cat > .env << 'EOF' | ||
| BACKEND_IMAGE=${{ needs.build-and-push.outputs.backend_image }} | ||
| FRONTEND_IMAGE=${{ needs.build-and-push.outputs.frontend_image }} | ||
| EOF | ||
| # Login to GHCR if credentials provided | ||
| if [ -n "${{ secrets.GHCR_USERNAME }}" ] && [ -n "${{ secrets.GHCR_TOKEN }}" ]; then | ||
| echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin | ||
| fi | ||
| # Ensure docker compose is available | ||
| (docker compose version || docker-compose version) | ||
| # Pull and run | ||
| (docker compose --env-file .env -f docker-compose.yml pull || docker-compose --env-file .env -f docker-compose.yml pull) | ||
| (docker compose --env-file .env -f docker-compose.yml up -d || docker-compose --env-file .env -f docker-compose.yml up -d) | ||
| docker image prune -f || true | ||
| # Basic health checks | ||
| curl -fsS http://localhost:8000/api/health || (echo 'Backend health failed' && exit 1) | ||
| curl -fsS http://localhost:3000/ || (echo 'Frontend health failed' && exit 1) | ||