Merge pull request #49 from telemetryflow/dependabot/npm_and_yarn/ope… #65
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
| # ============================================================================= | |
| # TelemetryFlow Core - Docker Image Builder Workflow (Makefile-Enhanced) | |
| # ============================================================================= | |
| # | |
| # TelemetryFlow Core - Identity and Access Management Service | |
| # Copyright (c) 2024-2026 DevOpsCorner Indonesia. All rights reserved. | |
| # | |
| # This workflow builds and publishes Docker images using Makefile targets: | |
| # - Consistent build process between local and CI environments | |
| # - Multi-platform support: linux/amd64, linux/arm64 | |
| # - Semantic versioning tags | |
| # - Docker Hub registry only | |
| # | |
| # Triggers: | |
| # - Push tags matching v*.*.* | |
| # - Push to main/master branch (latest tag) | |
| # - Manual workflow dispatch | |
| # | |
| # ============================================================================= | |
| name: Docker Build - TelemetryFlow Core | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - master | |
| tags: | |
| - 'v*.*.*' | |
| paths: | |
| - 'Dockerfile*' | |
| - 'src/**' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'tsconfig.json' | |
| - 'nest-cli.json' | |
| - '.github/workflows/docker.yml' | |
| pull_request: | |
| branches: | |
| - main | |
| - master | |
| paths: | |
| - 'Dockerfile*' | |
| - 'src/**' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'tsconfig.json' | |
| - 'nest-cli.json' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version tag (e.g., 1.0.0)' | |
| required: false | |
| default: '' | |
| push: | |
| description: 'Push images to registry' | |
| required: false | |
| type: boolean | |
| default: true | |
| platforms: | |
| description: 'Target platforms' | |
| required: false | |
| default: 'linux/amd64,linux/arm64' | |
| env: | |
| REGISTRY_DOCKER: docker.io | |
| IMAGE_NAME: telemetryflow/telemetryflow-core | |
| PRODUCT_NAME: TelemetryFlow Core | |
| permissions: | |
| contents: read | |
| id-token: write | |
| jobs: | |
| # =========================================================================== | |
| # Prepare Build Context | |
| # =========================================================================== | |
| prepare: | |
| name: Prepare Build | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.meta.outputs.version }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| commit: ${{ steps.info.outputs.commit }} | |
| branch: ${{ steps.info.outputs.branch }} | |
| build_time: ${{ steps.info.outputs.build_time }} | |
| go_version: ${{ steps.info.outputs.go_version }} | |
| push: ${{ steps.check.outputs.push }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '25' | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: '10.24.0' | |
| - name: Get build info | |
| id: info | |
| run: | | |
| echo "commit=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT | |
| echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT | |
| echo "build_time=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT | |
| # Get Node.js version | |
| echo "node_version=$(node --version)" >> $GITHUB_OUTPUT | |
| - name: Check if should push | |
| id: check | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| echo "push=false" >> $GITHUB_OUTPUT | |
| elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "push=${{ github.event.inputs.push }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "push=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: | | |
| ${{ env.REGISTRY_DOCKER }}/${{ env.IMAGE_NAME }} | |
| flavor: | | |
| latest=false | |
| tags: | | |
| # Semantic versioning from tags | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{major}} | |
| # Manual version input | |
| type=raw,value=${{ github.event.inputs.version }},enable=${{ github.event.inputs.version != '' }} | |
| # Branch name for non-tag pushes | |
| type=ref,event=branch | |
| # PR number | |
| type=ref,event=pr | |
| # Latest for default branch | |
| type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') || github.ref == format('refs/heads/{0}', 'master') }} | |
| # Git SHA | |
| type=sha,prefix=sha-,format=short | |
| labels: | | |
| org.opencontainers.image.title=${{ env.PRODUCT_NAME }} | |
| org.opencontainers.image.description=Identity and Access Management service for TelemetryFlow platform | |
| org.opencontainers.image.vendor=TelemetryFlow | |
| io.telemetryflow.product=${{ env.PRODUCT_NAME }} | |
| io.telemetryflow.component=iam-service | |
| io.telemetryflow.platform=CEOP | |
| # =========================================================================== | |
| # Build and Push Docker Image | |
| # =========================================================================== | |
| build: | |
| name: Build & Push | |
| runs-on: ubuntu-latest | |
| needs: prepare | |
| steps: | |
| - name: Free up disk space | |
| run: | | |
| echo "=== Disk space before cleanup ===" | |
| df -h | |
| # Remove unnecessary tools and SDKs | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf /usr/local/lib/android | |
| sudo rm -rf /opt/ghc | |
| sudo rm -rf /opt/hostedtoolcache/CodeQL | |
| sudo rm -rf /usr/local/share/boost | |
| sudo rm -rf /usr/share/swift | |
| sudo rm -rf "$AGENT_TOOLSDIRECTORY" | |
| # Clean apt cache | |
| sudo apt-get clean | |
| # Remove Docker images we don't need | |
| docker system prune -af --volumes || true | |
| echo "=== Disk space after cleanup ===" | |
| df -h | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Create Dockerfile | |
| run: | | |
| cat > Dockerfile << 'EOF' | |
| # ============================================================================= | |
| # TelemetryFlow Core - Multi-stage Dockerfile | |
| # ============================================================================= | |
| FROM node:25-alpine AS builder | |
| ARG VERSION=dev | |
| ARG GIT_COMMIT=unknown | |
| ARG GIT_BRANCH=unknown | |
| ARG BUILD_TIME=unknown | |
| WORKDIR /build | |
| # Install build dependencies | |
| RUN apk add --no-cache python3 g++ | |
| # Install pnpm | |
| RUN npm install -g pnpm@10.24.0 | |
| # Copy package files | |
| COPY package.json pnpm-lock.yaml ./ | |
| # Install dependencies | |
| RUN pnpm install --frozen-lockfile | |
| # Copy source | |
| COPY . . | |
| # Build application | |
| RUN pnpm build | |
| # ============================================================================= | |
| # Final image | |
| # ============================================================================= | |
| FROM node:25-alpine | |
| # Update packages to get security patches (CVE fixes) | |
| RUN apk upgrade --no-cache && \ | |
| apk add --no-cache ca-certificates tzdata curl | |
| # Create app user | |
| RUN addgroup -g 1001 -S nodejs && \ | |
| adduser -S nestjs -u 1001 | |
| WORKDIR /app | |
| # Copy built application | |
| COPY --from=builder --chown=nestjs:nodejs /build/dist ./dist | |
| COPY --from=builder --chown=nestjs:nodejs /build/node_modules ./node_modules | |
| COPY --from=builder --chown=nestjs:nodejs /build/package.json ./ | |
| # Create logs directory | |
| RUN mkdir -p logs && chown nestjs:nodejs logs | |
| # Labels | |
| LABEL org.opencontainers.image.title="TelemetryFlow Core" | |
| LABEL org.opencontainers.image.description="Identity and Access Management service for TelemetryFlow" | |
| LABEL org.opencontainers.image.vendor="TelemetryFlow" | |
| LABEL org.opencontainers.image.licenses="Apache-2.0" | |
| USER nestjs | |
| EXPOSE 3000 | |
| HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ | |
| CMD curl -f http://localhost:3000/health || exit 1 | |
| CMD ["node", "dist/main.js"] | |
| EOF | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to Docker Hub | |
| if: needs.prepare.outputs.push == 'true' && vars.DOCKERHUB_USERNAME != '' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY_DOCKER }} | |
| username: ${{ vars.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Determine version | |
| id: version | |
| run: | | |
| if [ "${{ github.event.inputs.version }}" != "" ]; then | |
| VERSION="${{ github.event.inputs.version }}" | |
| elif [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| else | |
| VERSION="${{ needs.prepare.outputs.commit }}" | |
| fi | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| - name: Build and push | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| platforms: ${{ github.event.inputs.platforms || 'linux/amd64,linux/arm64' }} | |
| push: ${{ needs.prepare.outputs.push == 'true' }} | |
| tags: ${{ needs.prepare.outputs.tags }} | |
| labels: ${{ needs.prepare.outputs.labels }} | |
| build-args: | | |
| VERSION=${{ steps.version.outputs.version }} | |
| GIT_COMMIT=${{ needs.prepare.outputs.commit }} | |
| GIT_BRANCH=${{ needs.prepare.outputs.branch }} | |
| BUILD_TIME=${{ needs.prepare.outputs.build_time }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| provenance: true | |
| sbom: true | |
| - name: Generate SBOM | |
| if: needs.prepare.outputs.push == 'true' | |
| uses: anchore/sbom-action@v0 | |
| with: | |
| image: ${{ env.REGISTRY_DOCKER }}/${{ env.IMAGE_NAME }}:sha-${{ needs.prepare.outputs.commit }} | |
| format: spdx-json | |
| output-file: sbom-${{ steps.version.outputs.version }}.spdx.json | |
| upload-release-assets: false | |
| - name: Upload SBOM | |
| if: needs.prepare.outputs.push == 'true' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: sbom-${{ needs.prepare.outputs.commit }} | |
| path: sbom-*.spdx.json | |
| retention-days: 90 | |
| # =========================================================================== | |
| # Security Scan | |
| # =========================================================================== | |
| scan: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| needs: [prepare, build] | |
| if: needs.build.result == 'success' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to Docker Hub | |
| if: needs.prepare.outputs.push == 'true' && vars.DOCKERHUB_USERNAME != '' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY_DOCKER }} | |
| username: ${{ vars.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Determine scan target | |
| id: scan-target | |
| run: | | |
| echo "=== Security Scan Configuration ===" | |
| echo "Repository: ${{ github.repository }}" | |
| echo "Commit: ${{ needs.prepare.outputs.commit }}" | |
| echo "Push enabled: ${{ needs.prepare.outputs.push }}" | |
| echo "Docker Hub Image: ${{ env.REGISTRY_DOCKER }}/${{ env.IMAGE_NAME }}" | |
| if [ "${{ needs.prepare.outputs.push }}" = "true" ]; then | |
| # Try to scan the pushed image | |
| IMAGE_REF="${{ env.REGISTRY_DOCKER }}/${{ env.IMAGE_NAME }}:sha-${{ needs.prepare.outputs.commit }}" | |
| echo "Checking if pushed image is available: $IMAGE_REF" | |
| # Try to pull the image to verify it exists | |
| if docker pull "$IMAGE_REF" 2>/dev/null; then | |
| echo "✅ Using pushed image: $IMAGE_REF" | |
| echo "target=$IMAGE_REF" >> $GITHUB_OUTPUT | |
| echo "type=remote" >> $GITHUB_OUTPUT | |
| else | |
| echo "⚠️ Pushed image not available, will build locally for scanning" | |
| echo "target=local-scan-image" >> $GITHUB_OUTPUT | |
| echo "type=local" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "📝 PR build - will build locally for scanning" | |
| echo "target=local-scan-image" >> $GITHUB_OUTPUT | |
| echo "type=local" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Build image for scanning (if needed) | |
| if: steps.scan-target.outputs.type == 'local' | |
| run: | | |
| # Create Dockerfile for scanning | |
| cat > Dockerfile << 'EOF' | |
| FROM node:25-alpine AS builder | |
| WORKDIR /build | |
| RUN apk add --no-cache python3 g++ | |
| RUN npm install -g pnpm@10.24.0 | |
| COPY package.json pnpm-lock.yaml ./ | |
| RUN pnpm install --frozen-lockfile | |
| COPY . . | |
| RUN pnpm build | |
| FROM node:25-alpine | |
| RUN apk upgrade --no-cache && \ | |
| apk add --no-cache ca-certificates tzdata curl | |
| RUN addgroup -g 1001 -S nodejs && \ | |
| adduser -S nestjs -u 1001 | |
| WORKDIR /app | |
| COPY --from=builder --chown=nestjs:nodejs /build/dist ./dist | |
| COPY --from=builder --chown=nestjs:nodejs /build/node_modules ./node_modules | |
| COPY --from=builder --chown=nestjs:nodejs /build/package.json ./ | |
| RUN mkdir -p logs && chown nestjs:nodejs logs | |
| USER nestjs | |
| EXPOSE 3000 | |
| CMD ["node", "dist/main.js"] | |
| EOF | |
| # Build image locally for scanning | |
| docker build -t local-scan-image . | |
| - name: Run Trivy vulnerability scanner | |
| id: trivy-scan | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| image-ref: ${{ steps.scan-target.outputs.target }} | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| severity: 'CRITICAL,HIGH' | |
| continue-on-error: true | |
| - name: Check scan results | |
| run: | | |
| if [ -f "trivy-results.sarif" ]; then | |
| echo "✅ Trivy scan completed successfully" | |
| echo "📄 SARIF file size: $(du -h trivy-results.sarif | cut -f1)" | |
| # Check if there are any findings | |
| if grep -q '"results"' trivy-results.sarif; then | |
| echo "🔍 Security findings detected" | |
| echo "📋 Summary of findings:" | |
| # Extract and display basic vulnerability info | |
| if command -v jq >/dev/null 2>&1; then | |
| jq -r '.runs[0].results[]? | "- \(.ruleId): \(.message.text)"' trivy-results.sarif | head -10 | |
| else | |
| echo "Install jq to see detailed findings" | |
| fi | |
| else | |
| echo "✅ No security vulnerabilities found" | |
| fi | |
| else | |
| echo "⚠️ Trivy scan did not produce results file" | |
| echo "This might indicate a scan failure or no vulnerabilities found" | |
| fi | |
| - name: Upload scan results as artifact | |
| uses: actions/upload-artifact@v6 | |
| if: always() && hashFiles('trivy-results.sarif') != '' | |
| with: | |
| name: trivy-scan-results-${{ needs.prepare.outputs.commit }} | |
| path: trivy-results.sarif | |
| retention-days: 30 | |
| # =========================================================================== | |
| # Summary | |
| # =========================================================================== | |
| summary: | |
| name: Build Summary | |
| runs-on: ubuntu-latest | |
| needs: [prepare, build] | |
| if: always() | |
| steps: | |
| - name: Summary | |
| run: | | |
| echo "## Docker Build Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Item | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Product** | ${{ env.PRODUCT_NAME }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Node.js Version** | ${{ needs.prepare.outputs.node_version }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Commit** | ${{ needs.prepare.outputs.commit }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Branch** | ${{ needs.prepare.outputs.branch }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Build Time** | ${{ needs.prepare.outputs.build_time }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Push** | ${{ needs.prepare.outputs.push }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Tags" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| echo "${{ needs.prepare.outputs.tags }}" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Pull Commands" >> $GITHUB_STEP_SUMMARY | |
| echo '```bash' >> $GITHUB_STEP_SUMMARY | |
| echo "# Docker Hub" >> $GITHUB_STEP_SUMMARY | |
| echo "docker pull ${{ env.IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Usage" >> $GITHUB_STEP_SUMMARY | |
| echo '```bash' >> $GITHUB_STEP_SUMMARY | |
| echo "# Run TelemetryFlow Core" >> $GITHUB_STEP_SUMMARY | |
| echo "docker run -d --name telemetryflow-core \\" >> $GITHUB_STEP_SUMMARY | |
| echo " -p 3000:3000 \\" >> $GITHUB_STEP_SUMMARY | |
| echo " -e POSTGRES_HOST=your-postgres-host \\" >> $GITHUB_STEP_SUMMARY | |
| echo " -e POSTGRES_PASSWORD=your-password \\" >> $GITHUB_STEP_SUMMARY | |
| echo " -e JWT_SECRET=your-jwt-secret \\" >> $GITHUB_STEP_SUMMARY | |
| echo " ${{ env.IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "# Health check" >> $GITHUB_STEP_SUMMARY | |
| echo "curl http://localhost:3000/health" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY |