name: CI/CD → Portainer on: push: branches: - main env: REGISTRY: ${{ vars.REGISTRY }} # ex: git.matheussouza.com.br/gitadmin BACKEND_IMAGE: saas-imobiliaria-backend FRONTEND_IMAGE: saas-imobiliaria-frontend jobs: # ──────────────────────────────────────────────────────────────────────────── # 1. BUILD & PUSH IMAGES # ──────────────────────────────────────────────────────────────────────────── build: name: Build & Push Docker Images runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set image tag id: tag run: echo "IMAGE_TAG=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT - name: Log in to Forgejo Container Registry uses: docker/login-action@v3 with: registry: ${{ vars.REGISTRY }} username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 # ── Backend ────────────────────────────────────────────────────────────── - name: Build & push backend uses: docker/build-push-action@v5 with: context: ./backend file: ./backend/Dockerfile.prod push: true tags: | ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:${{ steps.tag.outputs.IMAGE_TAG }} ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:latest cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:cache cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:cache,mode=max # ── Frontend ───────────────────────────────────────────────────────────── - name: Build & push frontend uses: docker/build-push-action@v5 with: context: ./frontend file: ./frontend/Dockerfile.prod push: true build-args: | VITE_API_URL=https://${{ vars.DOMAIN }}/api tags: | ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:${{ steps.tag.outputs.IMAGE_TAG }} ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:latest cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:cache cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:cache,mode=max outputs: image_tag: ${{ steps.tag.outputs.IMAGE_TAG }} # ──────────────────────────────────────────────────────────────────────────── # 2. DEPLOY VIA PORTAINER WEBHOOK # ──────────────────────────────────────────────────────────────────────────── deploy: name: Deploy to Portainer runs-on: ubuntu-latest needs: build steps: - name: Checkout uses: actions/checkout@v4 - name: Trigger Portainer stack deploy run: | curl -s -o /dev/null -w "%{http_code}" \ --fail \ -X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}" \ -H "Content-Type: application/json" \ -d '{ "env": [ {"name": "IMAGE_TAG", "value": "${{ needs.build.outputs.image_tag }}"}, {"name": "REGISTRY", "value": "${{ vars.REGISTRY }}"}, {"name": "DOMAIN", "value": "${{ vars.DOMAIN }}"}, {"name": "POSTGRES_DB", "value": "${{ secrets.POSTGRES_DB }}"}, {"name": "POSTGRES_USER", "value": "${{ secrets.POSTGRES_USER }}"}, {"name": "POSTGRES_PASSWORD", "value": "${{ secrets.POSTGRES_PASSWORD }}"}, {"name": "SECRET_KEY", "value": "${{ secrets.SECRET_KEY }}"}, {"name": "JWT_SECRET_KEY", "value": "${{ secrets.JWT_SECRET_KEY }}"} ] }' # ──────────────────────────────────────────────────────────────────────────── # 3. HEALTH CHECK (valida HTTPS + endpoints críticos) # ──────────────────────────────────────────────────────────────────────────── healthcheck: name: Validate HTTPS & Endpoints runs-on: ubuntu-latest needs: deploy steps: - name: Wait for containers to stabilize run: sleep 30 - name: Check frontend HTTPS run: | STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ --max-time 15 \ "https://${{ vars.DOMAIN }}") echo "Frontend status: $STATUS" [ "$STATUS" = "200" ] || (echo "Frontend HTTPS failed ($STATUS)" && exit 1) - name: Check HTTP → HTTPS redirect run: | LOCATION=$(curl -s -o /dev/null -w "%{redirect_url}" \ --max-time 10 \ "http://${{ vars.DOMAIN }}") echo "Redirect to: $LOCATION" echo "$LOCATION" | grep -q "https://" || (echo "HTTP redirect missing" && exit 1) - name: Check backend /health run: | STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ --max-time 15 \ "https://${{ vars.DOMAIN }}/api/health") echo "Backend health status: $STATUS" [ "$STATUS" = "200" ] || (echo "Backend health check failed ($STATUS)" && exit 1) - name: Check TLS certificate validity run: | EXPIRY=$(echo | openssl s_client -connect ${{ vars.DOMAIN }}:443 -servername ${{ vars.DOMAIN }} 2>/dev/null \ | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2) echo "Certificate expires: $EXPIRY" # Fail if cert expires in less than 7 days EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$EXPIRY" +%s) NOW_EPOCH=$(date +%s) DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 )) echo "Days until expiry: $DAYS_LEFT" [ "$DAYS_LEFT" -gt 7 ] || (echo "Certificate expires in $DAYS_LEFT days!" && exit 1) - name: Check API properties endpoint run: | STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ --max-time 15 \ "https://${{ vars.DOMAIN }}/api/properties?limit=1") echo "Properties API status: $STATUS" [ "$STATUS" = "200" ] || (echo "Properties API failed ($STATUS)" && exit 1) - name: Deployment validated successfully run: | echo "✅ All checks passed!" echo " → https://${{ vars.DOMAIN }} is live and healthy"