name: Build and Push Docker Image on: push: branches: [main] tags: ['v*.*.*'] env: REGISTRY: ghcr.io IMAGE_NAME: erp-mafia/gnubok jobs: build-and-push: runs-on: ubuntu-latest permissions: contents: read packages: write # OIDC token used by cosign keyless signing. id-token: write # SARIF upload to the repo's "Security" tab from the Trivy scan. security-events: write steps: - uses: actions/checkout@v6 - name: Log in to GHCR 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 }}/${{ env.IMAGE_NAME }} # On main: push `latest` + commit-sha tags. # On v*.*.* tags: push semver tags (1.2.3, 1.2, 1) for production pinning. tags: | type=raw,value=latest,enable={{is_default_branch}} type=sha,prefix= type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Build and push id: build uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | EXTENSIONS_PRESET=self-hosted # SBOM (software bill of materials) + SLSA provenance are attached as # OCI attestations, queryable via `docker buildx imagetools inspect`. provenance: mode=max sbom: true # Per-branch cache scope so a PR branch can't poison main's cache # layers. Fall back to main's cache on first build of a new branch. cache-from: | type=gha,scope=${{ github.ref_name }} type=gha,scope=main cache-to: type=gha,scope=${{ github.ref_name }},mode=max - name: Install cosign uses: sigstore/cosign-installer@v3.7.0 - name: Sign the image (keyless OIDC) env: DIGEST: ${{ steps.build.outputs.digest }} run: | cosign sign --yes "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}" - name: Scan image with Trivy id: trivy uses: aquasecurity/trivy-action@0.30.0 with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} # HIGH is blocking for an accounting application — Trivy's CRITICAL # bucket is narrow (mostly RCE-class), and HIGH covers everything # from auth bypass to crypto downgrades. ignore-unfixed keeps the # gate actionable: only CVEs we can patch by rebuilding fail the # pipeline, not upstream-pending issues we have no remediation for. severity: CRITICAL,HIGH exit-code: '1' ignore-unfixed: true format: sarif output: trivy-results.sarif - name: Upload Trivy results to GitHub Security tab # if:always() so a CRITICAL finding still ends up in the Security tab # even though the Trivy step above failed the workflow. if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: trivy-results.sarif category: trivy