GitLab CI to GitHub Actions Converter

Convert .gitlab-ci.yml to GitHub Actions workflow YAML in your browser. Translates stages, jobs, before_script, cache, artifacts, parallel matrix, and rules. Side-by-side diff, audit log, no upload.

Migrating from GitLab CI to GitHub Actions

GitLab CI organizes pipelines around stages and scripts; GitHub Actions organizes them around jobs and reusable Actions. Translating between the two is mostly mechanical, with two big exceptions: GitLab's implicit checkout becomes an explicit `actions/checkout@v4`, and GitLab's shell-script-only `script:` blocks must sometimes be split into multiple "step" entries (one per shell command) for clean GitHub Actions output.

The converter handles `stages`, `needs`, `rules`, `only/except`, `parallel:matrix`, `cache`, `artifacts`, `services`, and `variables`. It rewrites GitLab variables (`$CI_*`) to GitHub expressions (`${{ github.* }}`), and translates rule-based branch/tag filtering into the corresponding `on:` triggers and step-level `if:` conditions.

Syntax cheat sheet

ConceptGitLab CIGitHub Actions
Triggerworkflow: rules: - if: $CI_PIPELINE_SOURCE == "push"on: push:
Stagesstages: [test, deploy](implicit via `needs:`)
Jobtest: stage: test script: [npm test]jobs: test: runs-on: ubuntu-latest steps: - run: npm test
Imageimage: node:20container: node:20
Cachecache: key: foo paths: [node_modules]- uses: actions/cache@v4 with: key: foo path: node_modules
Artifactartifacts: paths: [dist]- uses: actions/upload-artifact@v4 with: name: dist path: dist
Variablesvariables: FOO: barenv: FOO: bar
Conditionalrules: - if: $CI_COMMIT_REF_NAME == "main"if: github.ref_name == 'main'

Gotchas to watch for

  • warning

    Add an explicit checkout

    GitLab clones automatically; GitHub Actions does not. The converter prepends `actions/checkout@v4` to every job that lacks one.

  • warning

    Stages disappear

    GitHub Actions has no notion of stages. The converter encodes stage order via `needs:` — the resulting graph is equivalent but the visualisation differs.

  • warning

    before_script becomes step lines

    GitLab `before_script:` lines are inlined as the first runs of the job's `steps:`, since there is no GitHub-level concept of before_script.

  • warning

    parallel:matrix → strategy.matrix

    The converter translates `parallel: matrix:` into GitHub Actions `strategy.matrix`. If you used numeric `parallel: 5`, it becomes `strategy.matrix.shard: [1,2,3,4,5]`.

  • warning

    Manual jobs need workflow_dispatch

    A GitLab job with `when: manual` becomes a GitHub Actions trigger of type `workflow_dispatch` plus a step-level conditional. The converter flags this for review.

Worked examples

Simple Node test → GitHub Actions

GitLab job becomes a single GitHub job with explicit checkout.

GitLab CI
stages:
  - test
test:
  stage: test
  image: node:20
  script:
    - npm ci
    - npm test
GitHub Actions
name: CI
on:
  push: {}
jobs:
  test:
    runs-on: ubuntu-latest
    container: node:20
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test

Build then deploy with rules

Stage order encoded via needs:; rules become a top-level on: trigger plus step-level if:.

GitLab CI
stages:
  - build
  - deploy
build:
  stage: build
  image: node:20
  script:
    - npm ci
    - npm run build
  artifacts:
    paths: [dist/]
deploy:
  stage: deploy
  needs: [build]
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  script:
    - aws s3 sync dist/ s3://my-bucket
GitHub Actions
name: CI
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    container: node:20
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/
  deploy:
    runs-on: ubuntu-latest
    needs: [build]
    if: github.ref_name == 'main'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: build
      - run: aws s3 sync dist/ s3://my-bucket

Frequently asked questions

Does it handle includes:?expand_more

GitLab `include:` (local files, project includes, remote URLs) is not auto-resolved. The converter operates on a single flattened YAML — paste the resolved YAML if your pipeline uses includes.

What about extends:?expand_more

Job templates extended via `extends:` are flagged for manual review. Inline the template into the job before converting, or replicate the template via a GitHub composite action after conversion.

How are services translated?expand_more

GitLab `services:` (e.g. postgres:15) become GitHub `services:` containers. The converter preserves the alias and image; you may need to adjust the env vars consumed by your scripts.

What replaces the GitLab Container Registry token?expand_more

GitLab's `$CI_REGISTRY_PASSWORD` has no GitHub equivalent. Use `${{ secrets.GITHUB_TOKEN }}` for GitHub Container Registry, or create a personal-access-token secret for Docker Hub.

Related Tools