GitHub Actions to GitLab CI Converter

Convert GitHub Actions workflows to GitLab CI .gitlab-ci.yml in your browser. Translates jobs, runners, matrix builds, caching, artifacts, and 50+ common Actions. Side-by-side diff, audit log, no upload.

Migrating from GitHub Actions to GitLab CI

Converting a GitHub Actions workflow to GitLab CI is mostly a matter of vocabulary, not semantics. Both platforms model jobs that run on a runner, depend on other jobs, and produce artifacts — but the keys, the expression syntax, and the way reusable steps are packaged are all different. The most surprising mismatches are around job dependencies (GitHub uses `needs:` while GitLab uses `needs:` *and* `stages:`), caching (an action vs. a job-level block), and Actions themselves (GitHub's `actions/*` ecosystem has no GitLab analog beyond shell commands).

The converter on this page parses your workflow into a canonical pipeline representation, then re-emits it as GitLab CI YAML. Every transformation is annotated with a confidence level so you know exactly what to verify. Common Actions like `actions/checkout`, `actions/setup-node`, `actions/cache`, and `actions/upload-artifact` are translated into native GitLab equivalents (auto-checkout, before_script installs, job-level cache and artifacts blocks). Less common Actions are translated into the equivalent shell commands, with the original reference preserved as a comment.

Syntax cheat sheet

ConceptGitHub ActionsGitLab CI
Top-level workflowname: CI on: push:workflow: rules: - if: $CI_PIPELINE_SOURCE == "push"
Branch filteron: push: branches: [main]rules: - if: $CI_COMMIT_BRANCH == "main"
Matrixstrategy: matrix: node: [18, 20, 22]parallel: matrix: - NODE: ["18", "20", "22"]
Runner imageruns-on: ubuntu-latestimage: ubuntu:22.04
Job dependencyneeds: [build]needs: [build] stage: deploy
Cache- uses: actions/cache@v4 with: key: ... path: node_modulescache: key: ... paths: [node_modules/]
Artifact- uses: actions/upload-artifact@v4 with: path: distartifacts: paths: [dist]
Secret reference${{ secrets.TOKEN }}$TOKEN
Conditionalif: github.ref_name == 'main'rules: - if: $CI_COMMIT_REF_NAME == "main"

Gotchas to watch for

  • warning

    GitLab checks out automatically

    There is no equivalent of `actions/checkout` — the runner clones the repository before the first script line. Drop the checkout step entirely.

  • warning

    Stages are mandatory for ordering

    GitLab uses both `stages:` and `needs:`. The converter assigns each job a stage based on its dependencies; keep the auto-generated `stages:` list at the top.

  • warning

    Cache is job-level, not a step

    The converter hoists every `actions/cache` invocation into a top-of-job `cache:` block. The original step becomes a comment, which you can safely remove.

  • warning

    Secrets are environment variables

    GitHub `${{ secrets.NAME }}` becomes plain `$NAME` in GitLab. Recreate every secret as a CI/CD variable in Settings → CI/CD → Variables.

  • warning

    Reusable workflows have no equivalent

    GitLab uses `include:` and `extends:` for reuse, with different semantics. The converter flags reusable-workflow calls for manual review.

Worked examples

Node.js test pipeline

A simple test job becomes a single GitLab job with the equivalent image and script.

GitHub Actions
name: CI
on:
  push:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test
GitLab CI
stages:
  - stage-0
test:
  stage: stage-0
  image: node:20
  script:
    - npm ci
    - npm test

Docker build + push

GitHub's `docker/build-push-action` becomes a buildx command. The login and setup actions become explicit shell steps.

GitHub Actions
name: Build
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: myorg/myapp:latest
GitLab CI
stages:
  - stage-0
build:
  stage: stage-0
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker buildx create --use --name builder
    - echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
    - docker buildx build --tag myorg/myapp:latest --push .

Frequently asked questions

Does the converter handle reusable workflows?expand_more

Reusable workflow invocations (`uses: org/repo/.github/workflows/foo.yml@ref`) are flagged as manual-review. GitLab's `include:` system works differently — you typically need to flatten reusable workflows into the parent or convert them to GitLab job templates with `extends:`.

What about composite actions?expand_more

Composite actions are also flagged for review. The recommended pattern on GitLab is a job template (a hidden `.template-job:`) referenced via `extends:`. The converter does not currently auto-flatten composites — paste the action's steps inline if you need a quick conversion.

How are GitHub-hosted runners mapped?expand_more

The converter sets the runner image to a sensible default (ubuntu:22.04 for `ubuntu-latest`). Override the `image:` to match your existing GitHub runner toolchain — node:20, python:3.11, etc.

What if my workflow uses `actions/github-script`?expand_more

There is no direct equivalent. The converter emits a TODO with a pointer to use the `gh` CLI or the @octokit/rest SDK. You will need to rewrite the JS by hand.

How are secrets handled?expand_more

Every detected `secrets.*` reference is added to the audit report as a checklist of secrets to recreate as GitLab CI/CD variables. The converter never sends secret names anywhere; the checklist is generated locally.

Related Tools