How to Migrate from Jenkins to GitHub Actions

Type: Software Reference Confidence: 0.93 Sources: 8 Verified: 2026-02-23 Freshness: monthly

TL;DR

Constraints

Quick Reference

Jenkins ConceptGitHub Actions EquivalentNotes
Jenkinsfile.github/workflows/*.ymlYAML vs Groovy [src1]
pipeline { }on: + jobs:Triggers + definitions [src1]
stage('Build')jobs: build:Each stage -> job [src1]
steps { sh 'cmd' }steps: - run: cmdDirect mapping [src1]
agent { docker {} }container:Or runs-on: for VM [src1]
when { branch 'main' }on: push: branches:Trigger filtering [src2]
parametersworkflow_dispatch: inputs:Manual trigger [src2]
environmentenv: (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/unstashupload-artifact / download-artifactBetween jobs [src2]
Shared librariesReusable workflows / composite actionsworkflow_call [src2]
1,800+ plugins15,000+ marketplace actionsImmutable actions in preview [src5, src8]
Agents/nodesGitHub-hosted or self-hosted runnersM2 macOS GA, Windows 2025 preview [src6]
Multibranch Pipelineon: 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 ($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

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

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

FeatureJenkinsGitHub ActionsNotes
Config formatGroovy DSL (Jenkinsfile)YAMLYAML anchors supported (2025)
ExecutionSelf-hosted (always)GitHub-hosted or self-hosted$0.002/min self-hosted from Mar 2026
Plugin ecosystem1,800+ plugins15,000+ marketplace actionsImmutable actions in preview
Cost modelFree (self-hosted infra)Free public; 2,000 min/mo privateHosted runner prices -39% Jan 2026
Matrix buildsmatrix { }strategy: matrix:fail-fast + max-parallel
Container supportDocker agentcontainer: keywordService containers also supported
Runner imagesCustom AMIs/Dockerubuntu-latest, windows-2025, macos-15M2 macOS GA, Win Server 2025 preview
CacheCustom solutionsactions/cache (>10 GB limit)Cross-branch sharing supported
Automated migrationN/AGitHub Actions Importer CLIaudit, dry-run, migrate commands

When to Use / When Not to Use

Migrate WhenDon't Migrate WhenUse Instead
Code is on GitHubCode on GitLab/BitbucketPlatform-native CI/CD
Jenkins maintenance is a burdenComplex shared library ecosystemPhased migration; keep Jenkins initially
Want managed CI/CD infrastructureNeed 100% on-premisesKeep Jenkins, Gitea Actions, or Drone
Team already uses GitHub ecosystemCustom hardware requirements (FPGA, GPU)Jenkins + specialized agents, hybrid
<2,000 min/mo private repo (free tier)Very high build volume on private reposCompare costs: Jenkins vs. GitHub Actions
Need OIDC-based cloud authRegulatory: all compute must be on-premJenkins with cloud-provider plugins

Important Caveats

Related Units