How to Migrate from Jenkins to GitHub Actions
How do I migrate from Jenkins to GitHub Actions?
TL;DR
- Bottom line: Map Jenkins stages to GitHub Actions jobs, replace plugins with marketplace actions, convert Groovy DSL to YAML -- most pipelines convert in 1-2 days. Use the GitHub Actions Importer CLI for automated initial conversion.
- Key tool/command:
gh actions-importer dry-run jenkins --source-url $JENKINS_URL --output-dir out/to auto-convert, then.github/workflows/ci.ymlreplaces yourJenkinsfile. - Watch out for: Jenkins shared libraries and custom Groovy have no direct equivalent -- extract logic into shell scripts or composite actions. The $0.002/min self-hosted-runner platform charge originally slated for March 2026 has been postponed per GitHub (2025-12-18) -- track the Changelog before factoring it into ROI.
- Works with: Any GitHub-hosted repo; Linux, macOS (M2 GA), Windows (Server 2025) runners; self-hosted for custom environments.
Constraints
- Pin third-party actions to full commit SHAs (not
@v*tags) -- immutable actions enforcement is rolling out on hosted runners in 2026. [src8] - Self-hosted runners on public repos must never run untrusted fork PRs -- use
pull_request_targetor restrict to private repos. [src6] - GitHub Actions has a 6-hour job timeout and 72-hour workflow timeout -- restructure long-running Jenkins jobs before migrating. [src2]
- GitHub-hosted runners provide 7 GB RAM / 2 CPU / 14 GB SSD by default -- use larger runners or self-hosted for resource-intensive builds. [src2]
- Secrets must never appear in workflow YAML -- use GitHub Secrets (repo/org/environment level) and OIDC federation for cloud providers. [src2]
- Self-hosted-runner $0.002/min platform charge originally planned for March 2026 has been postponed by GitHub (announced 2025-12-18) following community backlash -- do not budget around it without confirming current status in the GitHub Changelog. [src7, src9]
- Pin actions to immutable releases where available -- GitHub has begun migrating standard hosted runners to immutable action resolution, and the 2026 security roadmap adds a
dependencies:block in workflow YAML that locks transitive action SHAs. [src8, src10]
Quick Reference
| 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] |
Decision Tree
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 (the $0.002/min charge is currently POSTPONED but may return -- track Changelog) [src6, src9]
│ └── 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
Step-by-Step Guide
1. Audit and inventory your Jenkins pipelines
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.
2. Map Jenkinsfile to GitHub Actions YAML
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.
3. Migrate credentials and secrets
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.
4. Replace Jenkins plugins with GitHub Actions
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 }}
5. Convert shared libraries to reusable workflows
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.
6. Run Jenkins and GitHub Actions in parallel (validation phase)
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.
Code Examples
Bash: Automated Jenkinsfile to GitHub Actions converter
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)
YAML: Matrix build migration (Jenkins matrix -> GitHub Actions strategy)
# 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
YAML: OIDC authentication (replacing Jenkins cloud credentials)
# 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/
Anti-Patterns
Wrong: One massive job with all stages
# 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
Correct: Separate jobs with dependencies
# 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: [...]
Wrong: Hardcoding secrets in workflow files
# BAD -- secrets exposed in version control
env:
API_KEY: sk-12345abcdef
Correct: Use GitHub Secrets with OIDC where possible
# 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
Wrong: Using floating action tags
# BAD -- @v4 can change under you, supply chain risk [src8]
- uses: actions/checkout@v4
Correct: Pin actions to commit SHAs
# GOOD -- immutable, auditable, matches upcoming enforcement [src8]
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Wrong: Running self-hosted runners on public repos without restrictions
# BAD -- any fork PR can execute arbitrary code on your runner [src6]
on: pull_request
jobs:
build:
runs-on: self-hosted
Correct: Restrict self-hosted runners to trusted events
# 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
Common Pitfalls
- Shared libraries have no 1:1 equivalent: Extract Groovy logic into shell scripts or composite actions. Reusable workflows handle orchestration but cannot contain arbitrary Groovy. [src1, src3]
- Jenkins
whenconditions more flexible: GitHub Actionsif:covers most cases but cannot match complex Groovy conditionals. Fix: use job-level outputs and multi-step conditionals. [src1] - Artifact passing differs: Jenkins stash/unstash is within one pipeline. GitHub needs explicit upload/download between jobs. [src2]
- Cron syntax differences: Jenkins uses
Hfor hash distribution; GitHub uses standard cron. ReplaceHwith specific minutes. [src1] - Self-hosted runner security: GitHub self-hosted runners on public repos can execute code from any fork PR. Fix: use
pull_request_targetor restrict to private repos. [src6] - No built-in pipeline visualization: Jenkins Blue Ocean provides visual progress. Fix: use descriptive job/step names and group steps with composite actions. [src1]
- 2026 pricing volatility: GitHub announced (2025-12-16) then postponed (2025-12-18) the $0.002/min self-hosted-runner platform charge after community pushback; hosted-runner price cuts (up to 39% Jan 2026) did ship. The self-hosted fee may return in a different form. Fix: build cost models against current Changelog state, not against the original December 2025 announcement. [src7, src9]
- Actions Importer output needs review: The automated converter produces functional but not optimal YAML. Fix: always review -- merge redundant jobs, add caching, pin action versions. [src3]
Decision Logic
If Jenkins pipelines are simple (build + test + deploy, <10 stages, no shared libraries)
--> Convert the Jenkinsfile to a single .github/workflows/ci.yml by hand using the Quick Reference mapping table. Pipelines this size typically convert in <1 day. [src1]
If Jenkins pipelines are moderate (10-20 stages, 1-2 shared libraries, several plugins)
--> Run gh actions-importer dry-run jenkins to auto-generate a first-pass YAML, then refactor the output into reusable workflows for shared steps and composite actions for Groovy logic. [src3]
If Jenkins pipelines are complex (20+ stages, heavy shared libraries, custom Groovy plugins)
--> Use a phased migration: run Jenkins and GitHub Actions in parallel for 1-2 weeks, migrate one pipeline per sprint, and keep Jenkins authoritative until artifact checksums match across both. Do NOT attempt a big-bang cutover. [src3]
If the workload needs GPUs, special hardware, or on-prem network access
--> Configure self-hosted runners and pin to private repos or use pull_request_target with strict actor allowlists. NB: GitHub's original $0.002/min self-hosted-runner platform charge was postponed on 2025-12-18 -- track the GitHub Changelog before assuming current pricing. [src6, src9]
If the codebase is hosted on GitLab or Bitbucket (not GitHub)
--> Do NOT migrate to GitHub Actions; use the platform-native CI/CD (GitLab CI/CD or Bitbucket Pipelines). GitHub Actions cannot run against repositories outside GitHub. [src2]
If you need to enforce SHA-pinned third-party actions
--> Pin every uses: line to a full commit SHA (40 hex chars), enable repository rulesets that require immutable actions, and plan for the 2026 security roadmap dependencies: block once it ships. [src8, src10]
If a Jenkins shared library contains arbitrary Groovy logic
--> Do NOT try to translate Groovy into reusable-workflow YAML. Extract the logic into a shell script (committed to the shared repo) or a composite action under action.yml, then call it from the workflow. Reusable workflows are for orchestration, not arbitrary code. [src1, src3]
Diagnostic Commands
# 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
Version History & Compatibility
| Feature | Jenkins | GitHub Actions | Notes |
|---|---|---|---|
| Config format | Groovy DSL (Jenkinsfile) | YAML | YAML anchors supported (2025) |
| Execution | Self-hosted (always) | GitHub-hosted or self-hosted | Self-hosted $0.002/min charge POSTPONED 2025-12-18 |
| 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 |
When to Use / When Not to Use
| 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 |
Important Caveats
- GitHub Actions has a 6-hour job timeout and 72-hour workflow timeout. Long-running Jenkins jobs may need restructuring.
- GitHub-hosted runners: 7 GB RAM, 2 CPU, 14 GB SSD. Complex builds may need larger runners ($$$) or self-hosted. Larger runners (up to 64 CPU) available on Team and Enterprise plans.
- Free tier: 2,000 minutes/month for private repos. Hosted runner prices reduced up to 39% in January 2026 (this change shipped); the companion $0.002/min self-hosted platform charge was postponed on 2025-12-18 and remains under re-evaluation. Large orgs should compare total cost vs. self-hosted Jenkins. [src7, src9]
- Jenkins plugins with side effects (modifying Jenkins config, managing agents) have no equivalent -- those are Jenkins admin features.
- 67% of organizations using Jenkins as primary CI/CD in 2022 have migrated away or are in process (2024 State of DevOps Report).
- GitHub Actions Importer can automate initial conversion but output needs manual review and optimization. [src3]
- Agentic workflows (AI-powered automation in GitHub Actions) are in technical preview as of February 2026 -- not yet production-ready.