Skip to main content
This lesson shows how to migrate a Jenkins pipeline stage that runs the Trivy vulnerability scanner into an equivalent GitHub Actions job. The objective is to preserve behavior:
  • Scan the built Docker image,
  • Fail the pipeline when CRITICAL vulnerabilities are found,
  • Publish an HTML report for inspection.
Below we start with the relevant Jenkins stages, explain what they do, show the equivalent GitHub Actions step, and present a sample custom transformer (Ruby) used by migration tooling to automate the conversion.

Jenkins pipeline (source)

Here is the consolidated, relevant portion of the original Jenkinsfile. It includes three stages: Code Coverage, Build/Publish Image, and Trivy Vulnerability Scanner.
stage('Code Coverage') {
    agent {
        docker {
            image 'node:24'
            args '-u root:root'
        }
    }
    steps {
        catchError(buildResult: 'SUCCESS', message: 'Oops! it will be fixed in future releases', stageResult: 'UNSTABLE') {
            sh 'npm run coverage'
        }
        publishHTML([
            allowMissing: true,
            alwaysLinkToLastBuild: true,
            keepAll: true,
            reportDir: 'coverage/lcov-report',
            reportFiles: 'index.html',
            reportName: 'Coverage'
        ])
    }
}

stage('Build Publish Image') {
    steps {
        sh 'docker build -t siddharth67/solar-system:$GIT_COMMIT .'
        withDockerRegistry(credentialsId: 'docker-hub-credentials', url: "") {
            sh 'docker push siddharth67/solar-system:$GIT_COMMIT'
        }
    }
}

stage('Trivy Vulnerability Scanner') {
    steps {
        sh '''trivy image siddharth67/solar-system:$GIT_COMMIT --severity CRITICAL --exit-code 1 --quiet --format json -o trivy-image-CRITICAL-results.json'''
        sh '''trivy convert --format template --template "@/usr/local/share/trivy/templates/html.tpl" --output trivy-image-CRITICAL-results.html trivy-image-CRITICAL-results.json'''
        publishHTML([
            allowMissing: true,
            alwaysLinkToLastBuild: true,
            keepAll: true,
            reportDir: './',
            reportFiles: 'trivy-image-CRITICAL-results.html',
            reportName: 'Trivy CRITICAL'
        ])
    }
}
What these Jenkins sh steps do:
  • The trivy image command scans siddharth67/solar-system:$GIT_COMMIT with:
    • --severity CRITICAL (filter to CRITICAL)
    • --exit-code 1 (non-zero exit if CRITICAL vuln found)
    • --quiet (less noisy)
    • --format json -o <file> (JSON output saved)
  • The trivy convert command converts JSON into an HTML report using a template.

Why use the official Trivy GitHub Action?

The official action from Aqua Security (aquasecurity/trivy-action) can run Trivy on GitHub Actions runners and produce templated output directly (avoiding a separate convert step). It supports inputs such as format, template, output, severity, exit-code, and hide-progress.
Using aquasecurity/trivy-action lets you:
  • run Trivy without installing it manually on runners,
  • request templated HTML output directly (via format: template and template),
  • set exit-code so the job fails conditionally based on severity.
See the action documentation: https://github.com/aquasecurity/trivy-action
A dark-themed screenshot of a GitHub Marketplace / Actions page showing a table of inputs for the Aqua Security Trivy action, with columns for option name, type, default and description. The table lists parameters like vuln-type, severity, skip-dirs, cache-dir and a highlighted "hide-progress" row.

Mapping Jenkins flags to Trivy Action inputs

Use this quick reference when converting the Jenkins trivy shell invocation into action inputs.
Jenkins option / behaviorTrivy Action inputExample
--severity CRITICALseverityCRITICAL
--exit-code 1exit-code1
--quiethide-progresstrue
--format json -o file.json + trivy convertformat: template, template: <path>, outputformat: template, template: "@$HOME/.local/bin/trivy-bin/contrib/html.tpl", output: trivy-results.html
Image reference (e.g. siddharth67/solar-system:$GIT_COMMIT)image-ref${{ vars.DOCKERHUB_USERNAME }}/${{ vars.IMAGE_NAME }}:${{ github.sha }}
Notes:
  • Wrap special GitHub expressions and variables in backticks when showing in docs (e.g. ${{ github.sha }}).
  • Use format: template to make the action emit HTML directly, avoiding a separate conversion step.

Minimal GitHub Actions step equivalent

A compact Actions step that mirrors the Jenkins behavior and outputs an HTML report:
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@v0.30.0
  with:
    image-ref: "docker.io/my-organization/my-app:${{ github.sha }}"
    format: template
    template: "@$HOME/.local/bin/trivy-bin/contrib/html.tpl"
    output: trivy-results.html
    exit-code: '1'
    severity: "CRITICAL"
    hide-progress: true
Note: the template path above references the template bundled by the action. You can also provide a custom template file in your repository and point to it.

Automating the conversion: custom transformer (Ruby)

To automate migrating Jenkins sh steps to GitHub Actions, you can create a transformer that:
  • Detects sh calls that invoke trivy image,
  • Extracts the image reference, severity, format, output file, exit code, quiet flag,
  • Normalizes image references into repository variables (e.g. vars.DOCKERHUB_USERNAME, vars.IMAGE_NAME) and substitutes ${{ github.sha }},
  • Emits a aquasecurity/trivy-action step plus an actions/upload-artifact step that runs with if: ${{ always() }} so the report is uploaded even if the scan fails.
Example transformer used with migration tooling:
# ss-pipeline-transformer.rb
# Example transformer that converts Jenkins 'sh' trivy commands into GH Actions steps.
transform "sh" do |item|
  script = item.dig("arguments", 0, "value", "value").to_s.strip
  next nil unless script.downcase.start_with?("trivy image")

  # Extract image reference
  image_match = script.match(/trivy image (\S+)/i)
  image_ref = image_match ? image_match[1] : nil
  next nil unless image_ref

  # Extract other flags (fault-tolerant)
  severity = script.match(/--severity\s+(\S+)/i)&.captures&.first || "CRITICAL"
  format   = script.match(/--format\s+(\S+)/i)&.captures&.first || "json"
  output   = script.match(/-o\s+(\S+)/i)&.captures&.first || "trivy-results.json"
  exit_code = (script.match(/--exit-code\s+(\d+)/i)&.captures&.first || "1").to_i
  quiet = script.include?("--quiet") || script.include?("--quiet=true")

  # Normalize the image reference if it matches the original pattern
  if image_ref.include?("siddharth67/solar-system") || image_ref.include?("siddharth67/solar-system:$GIT_COMMIT")
    normalized_image_ref = "${{ vars.DOCKERHUB_USERNAME }}/${{ vars.IMAGE_NAME }}:${{ github.sha }}"
  else
    normalized_image_ref = image_ref
  end

  # Decide template-based output (we want HTML directly)
  # Use the Trivy action with template format and set the output to an HTML file
  trivy_output_html = output.end_with?(".html") ? output : "trivy-results.html"

  [
    {
      "name" => "Trivy Security Scan",
      "uses" => "aquasecurity/trivy-action@v0.30.0",
      "with" => {
        "image-ref" => normalized_image_ref,
        "severity"  => severity,
        "format"    => "template",
        "template"  => "@$HOME/.local/bin/trivy-bin/contrib/html.tpl",
        "output"    => trivy_output_html,
        "exit-code" => exit_code,
        "hide-progress" => quiet
      }
    },
    {
      "name" => "Upload Scan Report",
      "if" => "${{ always() }}",
      "uses" => "actions/upload-artifact@v4",
      "with" => {
        "name" => "Trivy Report",
        "path" => trivy_output_html
      }
    }
  ]
end
When the transformer runs as part of the migration tool, it replaces the Jenkins sh Trivy commands with the two GitHub Actions steps shown below.

Generated GitHub Actions job (migrated)

Example of the generated job after running the transformer:
Trivy_Vulnerability_Scanner:
  name: Trivy Vulnerability Scanner
  runs-on:
    - ubuntu-latest
  needs: Build_Publish_Image
  steps:
    - name: checkout
      uses: actions/checkout@v4.1.0
    - name: Trivy Security Scan
      uses: aquasecurity/trivy-action@v0.30.0
      with:
        image-ref: "${{ vars.DOCKERHUB_USERNAME }}/${{ vars.IMAGE_NAME }}:${{ github.sha }}"
        severity: CRITICAL
        format: template
        template: "@$HOME/.local/bin/trivy-bin/contrib/html.tpl"
        output: trivy-results.html
        exit-code: 1
        hide-progress: true
    - name: Upload Scan Report
      if: "${{ always() }}"
      uses: actions/upload-artifact@v4
      with:
        name: Trivy Report
        path: trivy-results.html
After the migrated workflow runs:
  • The Trivy action installs the Trivy binary, runs the scan, and writes trivy-results.html.
  • If no CRITICAL vulnerabilities are found, the job succeeds and the report is uploaded.
  • If CRITICAL vulnerabilities are found, the action exits with code 1 (job fails), but the upload step still runs because of if: ${{ always() }} so you can download the report.
A browser screenshot of a Trivy scan results page for "siddharth67/solar-system" showing "alpine" and "node-pkg" with "No Vulnerabilities found" and "No Misconfigurations found." A small download notification for "Trivy Report.zip" is visible in the top-right.

Best practices & tips

Summary

  • The Jenkins trivy image + trivy convert pipeline can be mapped directly to aquasecurity/trivy-action in GitHub Actions to simplify the workflow and avoid manual conversions.
  • A custom transformer can detect trivy shell commands and emit equivalent action steps while normalizing image references and preserving behavior (severity, exit-code, quiet).
  • Upload reports using actions/upload-artifact@v4 with if: ${{ always() }} so you can inspect results even on failures.
This approach keeps the original pipeline intent intact while producing a cleaner, maintainable workflow native to GitHub Actions.

Watch Video

Practice Lab