Skip to main content
Let’s create a custom transformer that recognizes and converts the embedded Docker build command inside Jenkins sh steps into a proper GitHub Actions step.
A blue-green gradient slide with the centered title "Custom Transformer Docker Build." A small "© Copyright KodeKloud" appears in the bottom-left corner.
Overview
  • Source: Jenkins pipeline builds and pushes a Docker image, then scans it with Trivy.
  • Goal: Convert the docker build command embedded in sh into a native GitHub Actions step and replace hard-coded values with repository/workflow variables.
Relevant Jenkinsfile stages (cleaned and complete)
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 Report'
        ])
    }
}

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 Report'
        ])
    }
}
Problem seen in the dry-run conversion
  • The docker build was inside an sh step and was not converted into a proper GitHub Actions run step.
  • The withDockerRegistry block had no matching transformer, so it was left commented out in the generated workflow.
Example of the dry-run output for the Build Publish Image job (trimmed):
if-no-files-found: ignore
name: Code Coverage HTML Report
path: coverage/lcov-report

Build_Publish_Image:
  name: Build Publish Image
  runs-on:
    - ubuntu-latest
  needs: Code_Coverage
  steps:
    - name: checkout
      uses: actions/checkout@v4.1.0

# This item has no matching transformer
# - withDockerRegistry:
#   - key: credentialsId
#     value:
#       isLiteral: true
#       value: docker-hub-credentials
#   - key: url
#     value:
#       isLiteral: true
Trivy_Vulnerability_Scanner:
  name: Trivy Vulnerability Scanner
  runs-on:
    - ubuntu-latest
  needs: Build_Publish_Image
Identifying which sh steps contain Docker commands
  • We ran a dry-run with a helper transformer that prints each sh identifier to locate the embedded Docker command.
Command used:
gh actions-importer dry-run jenkins \
  --source-url http://139.84.149.83:8080/job/ci-pipeline-poll-scm/ \
  --output-dir tmp/dry-run \
  --custom-transformers helper-transformer.rb
Example sh identifier JSON for the Docker build:
{
  "name": "sh",
  "arguments": [
    {
      "key": "script",
      "value": {
        "isLiteral": true,
        "value": "docker build -t siddharth67/solar-system:$GIT_COMMIT ."
      }
    }
  ]
}
This confirmed the docker build command appears in the sh identifier and is transformable. Custom sh transformer: extract and convert the docker build
  • We added a transformer that targets sh nodes and only transforms steps that match the specific docker build invocation for this repository/image.
  • The transformer substitutes hard-coded values with GitHub Actions variables, e.g. siddharth67vars.DOCKERHUB_USERNAME, solar-systemvars.IMAGE_NAME, and $GIT_COMMITgithub.sha.
Add this Ruby transformer (example name: ss-pipeline-transformer.rb):
transform "sh" do |item|
  # Safely extract the shell script from the 'sh' identifier
  script = item.dig("arguments", 0, "value", "value").to_s

  if script.include?("docker build") && script.include?("siddharth67/solar-system")
    # Replace hard-coded values with GitHub Actions variables
    converted_script = script
      .gsub('siddharth67', '${{ vars.DOCKERHUB_USERNAME }}')
      .gsub('solar-system', '${{ vars.IMAGE_NAME }}')
      .gsub('$GIT_COMMIT', '${{ github.sha }}')

    # Return a GitHub Actions step
    {
      "name" => "Build Docker image",
      "run" => converted_script,
      "shell" => "bash"
    }

  else
    # Skip transformation for certain known commands already handled elsewhere
    next nil if item.dig("arguments", 0, "value", "value") == "npm test"
  end
end
Notes on the transformer
  • Uses dig to safely navigate nested JSON and extract the script string.
  • Matches only the intended docker build for this repository to avoid false positives.
  • Substitutions convert Jenkins-specific/ hard-coded values to GitHub Actions expressions: use vars.DOCKERHUB_USERNAME, vars.IMAGE_NAME, and github.sha.
  • The transformer returns a hash that becomes a GitHub Actions step with name, run, and shell.
Re-running the dry-run (with this transformer)
gh actions-importer dry-run jenkins \
  --source-url http://139.84.149.83:8080/job/ci-pipeline-poll-scm/ \
  --output-dir tmp/dry-run \
  --custom-transformers ss-pipeline-transformer.rb
Result: The Build Publish Image job now contains a step named “Build Docker image” with the docker build command converted and variables substituted (no hard-coded credentials or commit tags remain). Best-practice mapping (quick reference)
Jenkins constructGitHub Actions equivalentNotes / Example
sh 'docker build -t owner/repo:$GIT_COMMIT .'run: docker build -t ${{ vars.DOCKERHUB_USERNAME }}/${{ vars.IMAGE_NAME }}:${{ github.sha }} .Replace owner, repo, and Jenkins variables with vars / github.sha.
withDockerRegistry(credentialsId: 'docker-hub-credentials')uses: docker/login-action@v2 or `run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login ...`Requires mapping Jenkins credentials to GitHub Secrets.
publishHTML([...])Upload artifact or use a GH Action that publishes HTML reportsUse actions/upload-artifact or a static-site publish workflow.
Define the repository or organization variables used by the transformer (for example, vars.DOCKERHUB_USERNAME and vars.IMAGE_NAME) in your GitHub repository or organization settings so the workflow can access them at runtime. For more, see Using variables in GitHub Actions.
Warning about credentials and registry authentication
Do not store credentials directly in workflows. Map Jenkins credentialsId to GitHub Secrets (e.g. secrets.DOCKERHUB_USERNAME and secrets.DOCKERHUB_TOKEN) or use docker/login-action with secure inputs. You will need a separate transformer for withDockerRegistry to perform this mapping correctly.
What remains to be done
  • Transform withDockerRegistry blocks:
    • Create a transformer that recognizes withDockerRegistry and emits a GitHub Actions step to authenticate to the container registry (for example, docker/login-action), mapping Jenkins credentials to secrets.*.
  • Ensure Trivy/report publishing:
    • If Trivy steps are in sh, they can be transformed similarly. Add steps to upload artifacts or publish HTML reports (e.g., actions/upload-artifact).
  • Validate end-to-end:
    • Run the importer in dry-run first, then with real output, and test the generated workflow in a branch.
References That’s it — we converted the embedded docker build in sh into a proper GitHub Actions run step and replaced hard-coded values with workflow variables. Next step: implement a transformer to convert withDockerRegistry into secure GitHub Actions authentication and finalize the push flow.

Watch Video