gh actions-importer dry-run jenkins --source-url $JENKINS_URL --output-dir out/ to auto-convert, then .github/workflows/ci.yml replaces your Jenkinsfile.@v* tags) -- immutable actions enforcement is rolling out on hosted runners in 2026. [src8]pull_request_target or restrict to private repos. [src6]| Jenkins Concept | GitHub Actions Equivalent | Notes |
|---|---|---|
Jenkinsfile | .github/workflows/*.yml | YAML vs Groovy [src1] |
pipeline { } | on: + jobs: | Triggers + definitions [src1] |
stage('Build') | jobs: build: | Each stage -> job [src1] |
steps { sh 'cmd' } | steps: - run: cmd | Direct mapping [src1] |
agent { docker {} } | container: | Or runs-on: for VM [src1] |
when { branch 'main' } | on: push: branches: | Trigger filtering [src2] |
parameters | workflow_dispatch: inputs: | Manual trigger [src2] |
environment | env: (workflow/job/step) | Scoped levels [src2] |
credentials() | ${{ secrets.NAME }} | Repo or org level [src2] |
post { always } | if: always() | Conditional execution [src1] |
parallel { } | Multiple jobs (default parallel) | Use needs: for ordering [src2] |
stash/unstash | upload-artifact / download-artifact | Between jobs [src2] |
| Shared libraries | Reusable workflows / composite actions | workflow_call [src2] |
| 1,800+ plugins | 15,000+ marketplace actions | Immutable actions in preview [src5, src8] |
| Agents/nodes | GitHub-hosted or self-hosted runners | M2 macOS GA, Windows 2025 preview [src6] |
Multibranch Pipeline | on: pull_request: | Auto-triggers on PRs [src2] |
cron('H/15 * * * *') | schedule: - cron: | Standard cron (no H hash) [src2] |
START
├── Is the code on GitHub?
│ ├── YES -> GitHub Actions is the natural choice [src2]
│ └── NO (GitLab/Bitbucket) -> Consider platform-native CI/CD
├── How complex are Jenkins pipelines?
│ ├── Simple (build+test+deploy, <10 stages) -> Direct YAML conversion [src1]
│ ├── Moderate (10-20 stages, some shared libs) -> Use Actions Importer + composite actions [src3]
│ └── Complex (heavy shared libs, custom plugins, 20+) -> Phased migration [src3]
├── Need self-hosted runners?
│ ├── YES (GPU, special hardware) -> Set up self-hosted ($0.002/min from Mar 2026) [src6, src7]
│ └── NO -> GitHub-hosted runners [src2]
├── Multi-repo or monorepo?
│ ├── Multi-repo -> One workflow per repo [src2]
│ └── Monorepo -> Path filters + reusable workflows [src2]
├── Need automated conversion?
│ ├── YES -> Run GitHub Actions Importer for initial conversion [src3]
│ └── NO -> Manual conversion for full control
└── DEFAULT -> Start with simplest pipeline, migrate incrementally
List all Jenkins jobs, their triggers, plugins, credentials, and build environments. Use the Actions Importer for an automated audit. [src3]
# Install GitHub Actions Importer
gh extension install github/gh-actions-importer
# Run audit to inventory all Jenkins pipelines
gh actions-importer audit jenkins \
--source-url http://jenkins.example.com \
--jenkins-access-token $JENKINS_TOKEN \
--output-dir audit-results/
# Review the audit summary
cat audit-results/audit_summary.md
Verify: Check audit-results/ for a complete list of pipelines and their complexity ratings.
Convert Groovy DSL to YAML. Each Jenkins stage becomes a job or a group of steps. [src1]
// BEFORE: Jenkinsfile
pipeline {
agent { docker { image 'node:20' } }
stages {
stage('Install') { steps { sh 'npm ci' } }
stage('Test') { steps { sh 'npm test' } }
stage('Build') { steps { sh 'npm run build' } }
stage('Deploy') {
when { branch 'main' }
steps {
withCredentials([string(credentialsId: 'deploy-token', variable: 'TOKEN')]) {
sh 'deploy.sh $TOKEN'
}
}
}
}
post {
always { junit '**/test-results/*.xml' }
failure { slackSend channel: '#alerts', message: 'Build failed!' }
}
}
# AFTER: .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
container: node:20
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
- run: npm run build
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: '**/test-results/*.xml'
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh ${{ secrets.DEPLOY_TOKEN }}
notify:
needs: [build-and-test, deploy]
if: failure()
runs-on: ubuntu-latest
steps:
- uses: slackapi/slack-github-action@v1
with:
channel-id: '#alerts'
slack-message: 'Build failed!'
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }}
Verify: actionlint .github/workflows/ci.yml -> no errors.
Move Jenkins credentials to GitHub Secrets. Use OIDC federation for cloud providers instead of long-lived keys. [src2]
# Move Jenkins credentials to GitHub Secrets
gh secret set DEPLOY_TOKEN --body "your-token-value"
gh secret set DOCKER_PASSWORD --body "your-password"
# Organization-wide secrets:
gh secret set ORG_SECRET --org my-org --body "value"
# Environment-specific secrets (staging vs production):
gh secret set AWS_ACCESS_KEY_ID --env production --body "AKIAIOSFODNN7EXAMPLE"
Verify: gh secret list -> all required secrets listed.
Map each Jenkins plugin to its marketplace equivalent. Pin to commit SHAs for security. [src5, src8]
# Docker build + push (Jenkins Docker plugin equivalent, pinned to SHA)
- uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
- uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@4f58ea79222b3b9dc7c8ddd91569dbe374b16e0d # v5.10.0
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
# SonarQube Scanner (Jenkins SonarQube plugin equivalent)
- uses: SonarSource/sonarcloud-github-action@v2
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Jenkins shared libraries -> GitHub reusable workflows or composite actions. [src2]
# .github/workflows/reusable-deploy.yml (in shared repo)
name: Reusable Deploy
on:
workflow_call:
inputs:
environment: { required: true, type: string }
secrets:
deploy-token: { required: true }
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh
env:
TOKEN: ${{ secrets.deploy-token }}
# Usage in another repo:
jobs:
deploy-staging:
uses: my-org/shared-workflows/.github/workflows/reusable-deploy.yml@main
with:
environment: staging
secrets:
deploy-token: ${{ secrets.DEPLOY_TOKEN }}
Verify: gh workflow list shows the reusable workflow; calling repo triggers it successfully.
Run both systems side-by-side for 1-2 weeks before decommissioning Jenkins. Compare build outputs, timing, and test results. [src3]
# Add a comparison step to your GitHub Actions workflow
compare-results:
needs: [build-and-test]
runs-on: ubuntu-latest
steps:
- name: Compare with Jenkins build
run: |
echo "GitHub Actions build: ${{ needs.build-and-test.result }}"
echo "Compare timing, test counts, and artifact checksums with Jenkins"
Verify: Both CI systems produce identical build artifacts and test results for at least 1 week.
Full script: bash-automated-jenkinsfile-to-github-actions-conve.sh (42 lines)
#!/bin/bash
# Input: Jenkinsfile path
# Output: Basic .github/workflows/ci.yml
set -euo pipefail
JENKINSFILE="${1:-Jenkinsfile}"
# ... (see full script)
# Jenkins: matrix { axes { axis { name 'OS'; values 'linux', 'windows' } } }
# GitHub Actions equivalent:
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm test
# Instead of storing AWS keys as secrets, use OIDC federation
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
aws-region: us-east-1
- run: aws s3 sync ./dist s3://my-bucket/
# BAD -- no parallelism, slow feedback, one failure blocks everything
jobs:
everything:
runs-on: ubuntu-latest
steps:
- run: npm ci && npm test && npm run build && npm run deploy
# GOOD -- parallel where possible, clear dependencies [src2]
jobs:
lint:
runs-on: ubuntu-latest
steps: [...]
test:
runs-on: ubuntu-latest
steps: [...]
deploy:
needs: [lint, test] # runs after both complete
runs-on: ubuntu-latest
steps: [...]
# BAD -- secrets exposed in version control
env:
API_KEY: sk-12345abcdef
# GOOD -- encrypted, never logged [src2]
env:
API_KEY: ${{ secrets.API_KEY }}
# BEST -- use OIDC for cloud providers (no stored secrets at all)
permissions:
id-token: write
# BAD -- @v4 can change under you, supply chain risk [src8]
- uses: actions/checkout@v4
# GOOD -- immutable, auditable, matches upcoming enforcement [src8]
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# BAD -- any fork PR can execute arbitrary code on your runner [src6]
on: pull_request
jobs:
build:
runs-on: self-hosted
# GOOD -- only trigger on internal events [src6]
on:
push:
branches: [main]
pull_request_target:
branches: [main]
jobs:
build:
runs-on: self-hosted
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
when conditions more flexible: GitHub Actions if: covers most cases but cannot match complex Groovy conditionals. Fix: use job-level outputs and multi-step conditionals. [src1]H for hash distribution; GitHub uses standard cron. Replace H with specific minutes. [src1]pull_request_target or restrict to private repos. [src6]# Analyze Jenkinsfile complexity
grep -c "stage\|step\|when\|parallel" Jenkinsfile
# List all Jenkins plugins used (Jenkins CLI)
java -jar jenkins-cli.jar -s http://jenkins:8080/ list-plugins
# Run GitHub Actions Importer dry-run
gh actions-importer dry-run jenkins \
--source-url http://jenkins.example.com \
--output-dir dry-run-results/
# Validate GitHub Actions workflow
actionlint .github/workflows/ci.yml
# List workflows and recent runs
gh workflow list && gh run list --limit 10
# Check self-hosted runner status
gh api /repos/{owner}/{repo}/actions/runners
# View workflow run logs
gh run view <run-id> --log
# Check GitHub Actions billing usage
gh api /orgs/{org}/settings/billing/actions
| Feature | Jenkins | GitHub Actions | Notes |
|---|---|---|---|
| Config format | Groovy DSL (Jenkinsfile) | YAML | YAML anchors supported (2025) |
| Execution | Self-hosted (always) | GitHub-hosted or self-hosted | $0.002/min self-hosted from Mar 2026 |
| Plugin ecosystem | 1,800+ plugins | 15,000+ marketplace actions | Immutable actions in preview |
| Cost model | Free (self-hosted infra) | Free public; 2,000 min/mo private | Hosted runner prices -39% Jan 2026 |
| Matrix builds | matrix { } | strategy: matrix: | fail-fast + max-parallel |
| Container support | Docker agent | container: keyword | Service containers also supported |
| Runner images | Custom AMIs/Docker | ubuntu-latest, windows-2025, macos-15 | M2 macOS GA, Win Server 2025 preview |
| Cache | Custom solutions | actions/cache (>10 GB limit) | Cross-branch sharing supported |
| Automated migration | N/A | GitHub Actions Importer CLI | audit, dry-run, migrate commands |
| Migrate When | Don't Migrate When | Use Instead |
|---|---|---|
| Code is on GitHub | Code on GitLab/Bitbucket | Platform-native CI/CD |
| Jenkins maintenance is a burden | Complex shared library ecosystem | Phased migration; keep Jenkins initially |
| Want managed CI/CD infrastructure | Need 100% on-premises | Keep Jenkins, Gitea Actions, or Drone |
| Team already uses GitHub ecosystem | Custom hardware requirements (FPGA, GPU) | Jenkins + specialized agents, hybrid |
| <2,000 min/mo private repo (free tier) | Very high build volume on private repos | Compare costs: Jenkins vs. GitHub Actions |
| Need OIDC-based cloud auth | Regulatory: all compute must be on-prem | Jenkins with cloud-provider plugins |