Skip to content

Latest commit

 

History

History
394 lines (324 loc) · 8.28 KB

File metadata and controls

394 lines (324 loc) · 8.28 KB

GitLab CI & Jenkins → GitHub Actions: Side-by-Side Comparison

TL;DR — The concepts are the same, the syntax differs, and the mental model flips: jobs default to parallel (use needs: to sequence) and each gets its own fresh VM (no shared workspace, no Groovy shared libraries — use reusable workflows or composite actions instead).

A practical referencefor translating GitLab CI pipelines and Jenkinsfile declarative pipelines into GitHub Actions workflows.


Basic Pipeline Structure

GitLab CI (.gitlab-ci.yml) Jenkinsfile (Declarative) GitHub Actions
variables:
  APP_VERSION: "1.0.0"

stages:
  - build
  - test
  - deploy

build:
  stage: build
  tags: [linux]
  script:
    - pip install -r requirements.txt

test:
  stage: test
  script:
    - pytest src/ --junitxml=report.xml
  artifacts:
    reports:
      junit: report.xml
    when: always

deploy:
  stage: deploy
  script:
    - ./scripts/deploy.sh
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
pipeline {
    agent { label 'linux' }

    environment {
        APP_VERSION = '1.0.0'
    }

    stages {
        stage('Build') {
            steps {
                sh 'pip install -r requirements.txt'
            }
        }

        stage('Test') {
            steps {
                sh 'pytest src/ --junitxml=report.xml'
            }
            post {
                always { junit 'report.xml' }
            }
        }

        stage('Deploy') {
            when { branch 'main' }
            steps {
                withCredentials([string(
                  credentialsId: 'DEPLOY_TOKEN',
                  variable: 'TOKEN')]) {
                    sh './scripts/deploy.sh'
                }
            }
        }
    }

    post {
        failure {
            mail to: 'team@securithings.com',
                 subject: 'Build failed'
        }
    }
}
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  APP_VERSION: "1.0.0"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4
      - run: pip install -r requirements.txt

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4
      - run: pytest src/ --junitxml=report.xml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results
          path: report.xml

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4
      - run: ./scripts/deploy.sh
        env:
          TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Common Patterns

Credentials / Secrets

GitLab CI Jenkins GitHub Actions
$MY_SECRET (CI/CD Variable, masked) withCredentials([string(...)]) ${{ secrets.MY_SECRET }} via env:
$CI_JOB_TOKEN (built-in) withCredentials([usernamePassword(...)]) Two separate secrets
Masked variable in Settings → CI/CD credentials('ID') in environment {} Secret scoped to repo / org / environment

Conditional Execution

GitLab CI Jenkins GitHub Actions
rules: - if: $CI_COMMIT_BRANCH == "main" when { branch 'main' } if: github.ref == 'refs/heads/main'
rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" when { changeRequest() } if: github.event_name == 'pull_request'
rules: - if: $MY_VAR == "true" when { expression { ... } } if: vars.MY_VAR == 'true'
rules: - when: never when { not { ... } } if: !condition

Parallel Execution

GitLab CI Jenkinsfile GitHub Actions
# Jobs in the same stage run in parallel
unit-test:
  stage: test
  script: pytest tests/unit/

integration-test:
  stage: test
  script: pytest tests/integration/
stage('Parallel Tests') {
    parallel {
        stage('Unit') {
            steps { sh 'pytest tests/unit/' }
        }
        stage('Integration') {
            steps { sh 'pytest tests/integration/' }
        }
    }
}
jobs:
  unit-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pytest tests/unit/

  integration-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pytest tests/integration/

  # Both run in parallel — no 'needs:' between them

Build Parameters → workflow_dispatch Inputs

GitLab CI Jenkinsfile GitHub Actions
# Triggered via API or UI with variables
variables:
  DEPLOY_ENV:
    value: "staging"
    description: "Target environment"
  DRY_RUN:
    value: "false"
    description: "Dry run mode"
parameters {
    string(
      name: 'DEPLOY_ENV',
      defaultValue: 'staging'
    )
    booleanParam(
      name: 'DRY_RUN',
      defaultValue: false
    )
}
on:
  workflow_dispatch:
    inputs:
      deploy_env:
        description: 'Target environment'
        default: 'staging'
        type: choice
        options: [staging, production]
      dry_run:
        description: 'Dry run mode'
        type: boolean
        default: false

Post / Always Actions

GitLab CI Jenkins post {} GitHub Actions
artifacts: when: always always {} if: always()
after_script: success {} if: success()
after_script: (check $CI_JOB_STATUS) failure {} if: failure()

Passing Files Between Jobs

GitLab CI Jenkins GitHub Actions
artifacts: paths: [dist/] stash includes: 'dist/**' actions/upload-artifact
dependencies: [build] unstash 'build' actions/download-artifact

Shared Libraries / Includes → Reusable Workflows

GitLab CI (include) Jenkins shared library GitHub Actions reusable workflow
# .gitlab-ci.yml (caller)
include:
  - project: 'org/shared-pipelines'
    file: '/deploy.yml'

deploy-staging:
  extends: .deploy
  variables:
    ENVIRONMENT: staging
// vars/deployApp.groovy
def call(String env) {
    sh "./deploy.sh ${env}"
}

// Caller (Jenkinsfile)
@Library('my-shared-lib') _

stage('Deploy') {
    steps { deployApp('staging') }
}
# .github/workflows/deploy.yml
on:
  workflow_call:
    inputs:
      environment:
        type: string
        required: true
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh ${{ inputs.environment }}

---
# Caller workflow
jobs:
  call-deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: staging

Key Mental Model Shifts

GitLab CI thinking Jenkins thinking GitHub Actions thinking
Stages run sequentially; jobs in same stage are parallel Stages run sequentially by default Jobs run in parallel by default — use needs: to sequence
Shared runner or group runner One agent per pipeline Each job gets its own fresh VM
.gitlab-ci.yml includes / extends Groovy shared libraries Reusable workflows & composite actions
CI/CD Variables (masked = secret) Credentials plugin Repository / Org secrets
environment: with manual approval Manual approval via Input step Environment protection rules (no plugin needed)
Job artifacts with expiry Build artifacts on Jenkins master Artifacts uploaded to GitHub (90-day retention)
$CI_COMMIT_SHA, $CI_BRANCH_NAME env.GIT_COMMIT, env.BRANCH_NAME ${{ github.sha }}, ${{ github.ref_name }}