Skip to main content
In this lesson we’ll migrate a simple Jenkins freestyle job into a GitHub Actions workflow. The goal is to replicate the Jenkins job behavior (a sequence of shell steps run on a single node) by creating a single-job workflow that can be manually triggered from the Actions UI. Below are the original Jenkins job and its build steps, followed by a concise GitHub Actions workflow that reproduces the same execution flow on ubuntu-latest.
A dark-themed Jenkins dashboard screenshot showing a list of CI pipeline jobs (ci-pipeline-poll-scm, Generate ASCII Artwork, scripted-pipeline, solar-system-ci-pipeline) with columns for last success, failure, and duration. The left sidebar shows navigation items like New Item, Build History, Manage Jenkins and a Build Queue panel.
This migration focuses on the “Generate ASCII Artwork” freestyle job. Open its configuration to inspect the build steps and options.
A screenshot of the Jenkins web UI showing the "Configure" page for a job (Generate ASCII Artwork) with the General settings panel, description field, and several option checkboxes. The sidebar lists other sections (Source Code Management, Triggers, Environment, Build Steps) and Save/Apply buttons are visible at the bottom.
Key characteristics of this Jenkins job:
  • No source control is configured (it was run manually in Jenkins).
  • No automated triggers (built manually).
  • No environment variables defined.
  • Several shell build steps that run sequentially on one build node.
A dark-themed Jenkins "Configure" settings screen showing the Triggers and Environment sections with multiple checkboxes (e.g., Poll SCM, Build periodically). The left sidebar lists job configuration tabs like General, Source Code Management, Triggers, and Build Steps.
Below is the core shell script the Jenkins job ran. It:
  • Calls the adviceslip API to fetch a piece of advice.
  • Validates the advice contains more than five words.
  • Installs cowsay and prints the advice as ASCII art.
# Build a message by invoking the adviceslip API
curl -s https://api.adviceslip.com/advice > advice.json
cat advice.json

# Extract the advice text and validate it has more than 5 words
jq -r .slip.advice < advice.json > advice.message
if [ "$(wc -w < advice.message)" -gt 5 ]; then
  echo "Advice has more than 5 words"
else
  echo "Advice - $(cat advice.message) has 5 words or less"
  exit 1
fi

# Install cowsay and display the advice as ASCII artwork
sudo apt-get update
sudo apt-get install -y cowsay jq
echo "$PATH"
export PATH="$PATH:/usr/games:/usr/local/games"
cat advice.message | cowsay -f "$(ls /usr/share/cowsay/cows | shuf -n 1)"
Migration approach summary:
  • Jenkins freestyle -> GitHub Actions single job.
  • Keep the same shell flow, but place it in one multi-line run step.
  • Use workflow_dispatch to allow manual triggering from the Actions UI.
  • Install required packages (jq, cowsay) on the runner before use.
Below is an example multi-job workflow (for reference) illustrating jobs, dependencies, environment variables, and artifacts in GitHub Actions:
name: Demo-2

on:
  workflow_dispatch:

env:
  GREETING: "Hello"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Create a text file
        run: echo "${{ env.GREETING }}, ${{ secrets.WORLD }}!" > hello.txt
      - uses: actions/upload-artifact@v4
        with:
          name: hello-file
          path: hello.txt

  test:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: hello-file
      - name: List files
        run: ls
      - name: Test file
        run: grep -i "Hello, world!" hello.txt
For our single-job migration, create a file at .github/workflows/generate-ascii.yaml with workflow_dispatch so it can be triggered manually. Here is a concise, robust workflow that reproduces the Jenkins behavior:
name: Generate ASCII Artwork

on:
  workflow_dispatch:

jobs:
  build-ascii:
    runs-on: ubuntu-latest
    steps:
      - name: Generate advice and show ASCII art
        run: |-
          # Install jq (used below) and cowsay so the runner has required tools
          sudo apt-get update
          sudo apt-get install -y jq cowsay

          # Build a message by invoking the adviceslip API
          curl -s https://api.adviceslip.com/advice > advice.json
          cat advice.json

          # Extract the advice text and validate it has more than 5 words
          jq -r .slip.advice < advice.json > advice.message
          if [ "$(wc -w < advice.message)" -gt 5 ]; then
            echo "Advice has more than 5 words"
          else
            echo "Advice - $(cat advice.message) has 5 words or less"
            exit 1
          fi

          # Display the advice as ASCII artwork
          echo "$PATH"
          export PATH="$PATH:/usr/games:/usr/local/games"
          cat advice.message | cowsay -f "$(ls /usr/share/cowsay/cows | shuf -n 1)"
Best practice: Put package installation at the top of your step so required tools are available before they are referenced. If you need persistent environment variables or secrets, declare them at the workflow or job level using env: or GitHub Secrets.
Quick mapping: Jenkins freestyle fields to GitHub Actions equivalents
Jenkins conceptGitHub Actions equivalentExample
Manual job runworkflow_dispatch triggeron: workflow_dispatch:
Shell build steps (sequential)Single job with a multi-line run stepUse `run:-` and paste the script
Environment variables at job levelenv: on workflow/jobenv: GREETING: "Hello"
Artifacts uploaded for downstream jobsactions/upload-artifact / actions/download-artifactuses: actions/upload-artifact@v4
Open the Actions tab in your repository and trigger the workflow manually. The job log will show output for each command just like Jenkins.
A dark-theme GitHub Actions page showing a repository's workflows and sidebar (including a "Generate ASCII Artwork" entry). The main panel lists recent workflow runs (several "Demo-2" entries) with success/failure icons, branch tags and timestamps.
Example log snippets you’ll see:
  • The adviceslip JSON and the validation success message:
{"slip":{"id":162,"advice":"Stop using the term \"busy\" as an excuse."}}
Advice has more than 5 words
  • Installation output and the generated ASCII artwork:
Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
  cowsay
0 upgraded, 1 newly installed, 0 to remove and 13 not upgraded.
Fetched 18.6 kB in 0s (156 kB/s)
Selecting previously unselected package cowsay.
(Reading database ...)
Unpacking cowsay (3.03+dfsg2-8) ...
Setting up cowsay (3.03+dfsg2-8) ...
/ Stop using the term "busy" as an \
\ excuse. /
-------------------------------
\
 \
/@   ~-.
/\  _-- |
 // // @
If the advice has five words or fewer, the step prints the advice and exits with code 1, causing the job to fail. Example failure output:
{"slip":{"id":6,"advice":"Never cut your own fringe."}}
Advice - Never cut your own fringe. has 5 words or less
Error: Process completed with exit code 1.
Note: When a script exits with a non-zero status, GitHub Actions marks the step and job as failed. Design your exit codes intentionally if you rely on failure vs. success conditions for downstream steps or notifications.
References and useful links: Next steps:
  • If you need to preserve job-level environment variables, add env: at the workflow or job level.
  • To split work into stages, convert sequential shell steps into separate jobs and use needs: to express dependencies.
  • A follow-up lesson will cover converting a multi-stage Jenkins pipeline into a multi-job GitHub Actions workflow.

Watch Video