name: Docker Build and Push on: push: branches: - main # Production builds - develop # Staging builds tags: - 'v*' # Version tags (v1.0.0, v1.2.3, etc.) workflow_dispatch: # Manual trigger env: REGISTRY: docker.io REPO: xpeditis jobs: # ================================================================ # Determine Environment # ================================================================ prepare: name: Prepare Build runs-on: ubuntu-latest outputs: environment: ${{ steps.set-env.outputs.environment }} backend_tag: ${{ steps.set-tags.outputs.backend_tag }} frontend_tag: ${{ steps.set-tags.outputs.frontend_tag }} should_push: ${{ steps.set-push.outputs.should_push }} steps: - name: Determine environment id: set-env run: | if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == refs/tags/v* ]]; then echo "environment=production" >> $GITHUB_OUTPUT else echo "environment=staging" >> $GITHUB_OUTPUT fi - name: Determine tags id: set-tags run: | if [[ "${{ github.ref }}" == refs/tags/v* ]]; then VERSION=${GITHUB_REF#refs/tags/v} echo "backend_tag=${VERSION}" >> $GITHUB_OUTPUT echo "frontend_tag=${VERSION}" >> $GITHUB_OUTPUT elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then echo "backend_tag=latest" >> $GITHUB_OUTPUT echo "frontend_tag=latest" >> $GITHUB_OUTPUT else echo "backend_tag=staging-latest" >> $GITHUB_OUTPUT echo "frontend_tag=staging-latest" >> $GITHUB_OUTPUT fi - name: Determine push id: set-push run: | # Push only on main, develop, or tags (not on PRs) if [[ "${{ github.event_name }}" == "push" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then echo "should_push=true" >> $GITHUB_OUTPUT else echo "should_push=false" >> $GITHUB_OUTPUT fi # ================================================================ # Build and Push Backend Image # ================================================================ build-backend: name: Build Backend Docker Image runs-on: ubuntu-latest needs: prepare steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub if: needs.prepare.outputs.should_push == 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.REPO }}/backend tags: | type=raw,value=${{ needs.prepare.outputs.backend_tag }} type=raw,value=build-${{ github.run_number }} type=sha,prefix={{branch}}- - name: Build and push Backend uses: docker/build-push-action@v5 with: context: ./apps/backend file: ./apps/backend/Dockerfile platforms: linux/amd64 push: ${{ needs.prepare.outputs.should_push == 'true' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max build-args: | NODE_ENV=${{ needs.prepare.outputs.environment }} - name: Image digest run: echo "Backend image digest ${{ steps.build.outputs.digest }}" # ================================================================ # Build and Push Frontend Image # ================================================================ build-frontend: name: Build Frontend Docker Image runs-on: ubuntu-latest needs: prepare steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub if: needs.prepare.outputs.should_push == 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Set environment variables id: env-vars run: | if [[ "${{ needs.prepare.outputs.environment }}" == "production" ]]; then echo "api_url=https://api.xpeditis.com" >> $GITHUB_OUTPUT echo "app_url=https://xpeditis.com" >> $GITHUB_OUTPUT echo "sentry_env=production" >> $GITHUB_OUTPUT else echo "api_url=https://api-staging.xpeditis.com" >> $GITHUB_OUTPUT echo "app_url=https://staging.xpeditis.com" >> $GITHUB_OUTPUT echo "sentry_env=staging" >> $GITHUB_OUTPUT fi - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.REPO }}/frontend tags: | type=raw,value=${{ needs.prepare.outputs.frontend_tag }} type=raw,value=build-${{ github.run_number }} type=sha,prefix={{branch}}- - name: Build and push Frontend uses: docker/build-push-action@v5 with: context: ./apps/frontend file: ./apps/frontend/Dockerfile platforms: linux/amd64 push: ${{ needs.prepare.outputs.should_push == 'true' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max build-args: | NEXT_PUBLIC_API_URL=${{ steps.env-vars.outputs.api_url }} NEXT_PUBLIC_APP_URL=${{ steps.env-vars.outputs.app_url }} NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }} NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ steps.env-vars.outputs.sentry_env }} NEXT_PUBLIC_GA_MEASUREMENT_ID=${{ secrets.NEXT_PUBLIC_GA_MEASUREMENT_ID }} - name: Image digest run: echo "Frontend image digest ${{ steps.build.outputs.digest }}" # ================================================================ # Security Scan (optional but recommended) # ================================================================ security-scan: name: Security Scan runs-on: ubuntu-latest needs: [build-backend, build-frontend, prepare] if: needs.prepare.outputs.should_push == 'true' strategy: matrix: service: [backend, frontend] steps: - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.REGISTRY }}/${{ env.REPO }}/${{ matrix.service }}:${{ matrix.service == 'backend' && needs.prepare.outputs.backend_tag || needs.prepare.outputs.frontend_tag }} format: 'sarif' output: 'trivy-results-${{ matrix.service }}.sarif' - name: Upload Trivy results to GitHub Security uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results-${{ matrix.service }}.sarif' # ================================================================ # Summary # ================================================================ summary: name: Build Summary runs-on: ubuntu-latest needs: [prepare, build-backend, build-frontend] if: always() steps: - name: Build summary run: | echo "## đŸŗ Docker Build Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Environment**: ${{ needs.prepare.outputs.environment }}" >> $GITHUB_STEP_SUMMARY echo "**Branch**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY echo "**Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Images Built" >> $GITHUB_STEP_SUMMARY echo "- Backend: \`${{ env.REGISTRY }}/${{ env.REPO }}/backend:${{ needs.prepare.outputs.backend_tag }}\`" >> $GITHUB_STEP_SUMMARY echo "- Frontend: \`${{ env.REGISTRY }}/${{ env.REPO }}/frontend:${{ needs.prepare.outputs.frontend_tag }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [[ "${{ needs.prepare.outputs.should_push }}" == "true" ]]; then echo "✅ Images pushed to Docker Hub" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Deploy with Portainer" >> $GITHUB_STEP_SUMMARY echo "1. Login to Portainer UI" >> $GITHUB_STEP_SUMMARY echo "2. Go to Stacks → Select \`xpeditis-${{ needs.prepare.outputs.environment }}\`" >> $GITHUB_STEP_SUMMARY echo "3. Click \"Editor\"" >> $GITHUB_STEP_SUMMARY echo "4. Update image tags if needed" >> $GITHUB_STEP_SUMMARY echo "5. Click \"Update the stack\"" >> $GITHUB_STEP_SUMMARY else echo "â„šī¸ Images built but not pushed (PR or dry-run)" >> $GITHUB_STEP_SUMMARY fi