GitHub-to-GitLab migration the easy way

· GitLab ·

11 min read Original article ↗

If you're evaluating a move to GitLab, the first question is almost always the same: How hard is the migration? For most DevSecOps teams, the fear of "heavy lifting" is the biggest obstacle between them and a single, AI-native DevSecOps platform.

Migrating to GitLab is easier than ever. GitLab's built-in importer moves the vast majority of your project data automatically and in the background. Additionally, translating your CI/CD pipeline, one part that has traditionally required a human, is now largely handled by GitLab Duo. Duo, our AI-native assistant, can convert GitHub Actions workflows into GitLab CI/CD.

In this post, I'll walk through the full migration end to end:

Let's get started.

What data migrates from GitHub to GitLab?

GitLab's built-in GitHub importer is accessed directly from GitLab's project creation UI, and it runs as a background job, meaning you can kick off an import and walk away. Most of your project data comes across automatically. A smaller set of items are optional toggles or have caveats worth knowing about up front.

The table below breaks down what migrates and how:

DataStatus
Git repository, branches, tags, and commit history✅ Automatic. Includes fork branches for open pull requests.
Issues✅ Automatic
Pull requests (become merge requests)✅ Automatic. Includes reviews, review comments, suggestions, assigned reviewers, and "merged by" info.
Issue and pull request comments✅ Automatic
Labels and milestones✅ Automatic
Release notes content✅ Automatic
Wiki pages✅ Automatic
Branch protection rules✅ Automatic
Issue and pull request events✅ Automatic
Collaborators (members)⚠️ With caveats. Optional toggle (on by default). Requires the read:org scope; GitHub roles are mapped to GitLab roles (see below). GitHub Enterprise Cloud custom roles aren't supported and are added manually.
Markdown attachments (in descriptions, comments, releases)⚠️ With caveats. Optional toggle. Attachments in private repos from before May 2023 can't be imported (a GitHub limitation); GitHub Enterprise Server imports only images and video.
Large comment volumes (~30,000+)⚠️ With caveats. Enable the alternative comments import to work around GitHub's per-issue API limits. Comments from before 2017 may import as separate threads.
Git LFS objects⚠️ With caveats. LFS must be enabled on the destination project before the import runs, or objects are silently skipped.
GitHub Actions workflows🛠️ Manual (AI-assisted). Converted to .gitlab-ci.yml. GitLab Duo can do most of this for you.
Secrets → CI/CD variables🛠️ Manual. Recreate secrets as CI/CD variables (mark them masked/protected) before running pipelines.
Required status checks🛠️ Manual. Recreate as external status checks.
GitHub Organizations / groupings❌ Not migrated. Structure GitLab groups and subgroups to match.

How roles are mapped

GitHub and GitLab use different naming conventions, so a mapping is performed during migration. When collaborators are imported, GitHub roles map to GitLab roles as follows:

GitHub roleGitLab role
ReadGuest
TriageReporter
WriteDeveloper
MaintainMaintainer
AdminOwner

Prerequisites

The prerequisites have gotten simpler over the years. Notably, you no longer need every GitHub author to have a matching public email address on GitLab. GitLab now handles attribution automatically with user contribution mapping (GitLab 17.8+). More on that below.

To import from GitHub.com or GitHub Enterprise Server into either GitLab.com or a GitLab Self-Managed instance, you need:

  • Access to the source GitHub project you want to import.
  • The Maintainer or Owner role on the destination GitLab group.
  • The GitHub import source enabled. This is on by default on GitLab.com; on GitLab Self-Managed, an administrator enables it under import sources.
  • The GitHub organization must not restrict third-party application access (or you must grant access to it during authorization).

A couple of situational prerequisites:

  • Importing Git LFS objects? Enable LFS on the destination project before you import.
  • Importing collaborators? You need the read:org scope on your token and at least Write or Maintain access on the GitHub project.

You don't need matching emails anymore

Previously, contribution history only carried over cleanly if each GitHub user's public email matched their GitLab email. Today, GitLab creates placeholder users for any GitHub author, assignee, or reviewer without a matching GitLab account, and preserves their contributions.

After the import, a group Owner or Maintainer goes to Members → Placeholders and reassigns each placeholder to the real GitLab user, who then accepts the reassignment. This means you can migrate first and sort out attribution afterward.

Performing the import

There are three ways to run the import. Pick the one that fits your source and scale.

This is the fastest path for most teams.

  1. Go to a group and select Create project → Import project → GitHub (or start directly at gitlab.com/projects/new#import_project).
  2. Press the Authenticate with GitHub button and approve the OAuth application.
  3. For organization repositories, click Grant next to the organization name to authorize access.
  4. Filter your repositories using the Owner / Collaborated / Organization tabs, then select the repositories to import. You can rename them and choose the target GitLab namespace.
  5. Configure the optional toggles (see table below), then click Import. The import runs as a background job; you can navigate away and check back later.

Optional toggles:

ToggleDefaultUse it when
Import collaboratorsOnYou want project members brought over with role mapping.
Import Markdown attachmentsOffYou want images and files embedded in descriptions, comments, and releases.
Use alternative comments importOffYour project has ~30,000+ comments and you're hitting GitHub API limits.

Method 2: Personal access token (when OAuth isn't configured)

If OAuth isn't configured, authenticate with a token instead.

  1. Create a classic personal access token at github.com/settings/tokens/new with the repo scope (add read:org if you're importing collaborators or LFS). Note: Fine-grained tokens aren't supported.
  2. In GitLab, go to a group and select Create project → Import project → GitHub.
  3. Paste the token, select Authenticate, then complete the same selection and configuration steps as the OAuth method.

Method 3: The REST API (bulk imports and scripting)

For migrating many repositories at once, scripting a move, or importing public repos you don't own, use the import API.

      curl --request POST \
  --url "https://gitlab.com/api/v4/import/github" \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "personal_access_token": "<github_classic_pat>",
    "repo_id": 12345678,
    "target_namespace": "my-group",
    "new_name": "imported-project",
    "optional_stages": {
      "single_endpoint_notes_import": true,
      "attachments_import": true,
      "collaborators_import": true
    }
  }'

    

Track progress with:

      curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.com/api/v4/projects/<project_id>/import"

    

You'll need a GitHub classic PAT with the repo scope plus a GitLab PAT with the api scope.

Verifying the import

Once the import finishes, do a quick sanity check:

  1. Open the new project and watch the status banner move through scheduled → started → finished. If it reports partially completed, drill into which entities failed.
  2. Compare counts of branches, tags, commit SHAs, issues, merge requests, labels, and milestones against the source.
  3. Confirm imported items carry an Imported badge.

Re-importing creates a fresh copy (you can't import into an existing project), so if something looks off, delete and re-run.

Running GitHub and GitLab together during a phased migration

Migration doesn't have to be a hard cutover. Most teams move gradually, keeping GitHub in play while they stand up GitLab, validate their pipelines, and bring people over team by team. GitLab is designed to coexist with GitHub during that transition, so you can adopt it incrementally instead of flipping a switch overnight.

Here are the main ways the two platforms work side by side while you migrate:

  • Keep repositories in sync with pull mirroring. GitLab can pull mirror a GitHub repository, automatically pulling new branches and commits on a schedule. Developers can keep pushing to GitHub while GitLab stays up to date, giving you a live, read-only copy to test CI/CD and workflows against before anyone changes their daily habits. When you're ready to cut over, you can switch to push mirroring so changes made in GitLab flow back to GitHub for teams still working there.
  • Run pipelines on GitHub repos with CI/CD for external repositories. If you want to evaluate GitLab CI/CD before moving the code, CI/CD for external repositories connects a GitHub repository to GitLab and runs pipelines on every push and pull request, reporting status back to GitHub. This lets you prove out your .gitlab-ci.yml conversion against real commits while the source of truth still lives in GitHub.
  • Post CI/CD status back to GitHub. With the GitHub integration, GitLab sends pipeline and commit status updates to GitHub, so developers who haven't moved yet still see green checks and build results in the interface they already use.
  • Migrate team by team, repository by repository. Because the importer works per project, you can move one team's repositories first, let them settle in, and bring the next group over once you've ironed out any wrinkles. Groups and subgroups that aren't migrated yet can keep pointing at GitHub in the meantime.

Migrating GitHub Actions to GitLab CI/CD

CI/CD is one part of the migration that isn't automatic. It's your chance to modernize your pipelines. Many core concepts map cleanly between the two platforms:

GitHub ActionsGitLab CI/CD
.github/workflows/*.yml.gitlab-ci.yml
WorkflowPipeline
JobJob
Step in a jobLine in script:
Event triggers (on:)rules: / workflow:
runs-on: / container:image: and runner tags:
Actions MarketplaceCI/CD Components
SecretsCI/CD variables
strategy.matrixparallel.matrix
actions/checkoutBuilt in (GitLab clones automatically)
actions/cachecache: keyword
actions/upload-artifactartifacts: keyword

A key difference to keep in mind: In GitLab, stages run sequentially and jobs within a stage run in parallel, and you can use needs: to build an explicit Directed Acyclic Graph (DAG).

The fast way: Let GitLab Duo convert your workflows

GitLab Duo Agent Platform includes a Convert to GitLab CI/CD flow that translates your GitHub Actions workflows into .gitlab-ci.yml for you, so you're reviewing a draft instead of rewriting from scratch.

GitLab Duo helps across the whole migration, not just conversion:

  • Planning — generate a per-repository migration plan and complexity assessment from your workflow files.
  • Conversion — the Convert to GitLab CI/CD flow rewrites Actions workflows into GitLab CI/CD, preserving matrix builds, job dependencies, artifacts, and conditional rules.
  • Documentation — update READMEs, badges, and links (for example, "Pull Request" → "Merge Request").
  • Validation — cross-check imported counts and surface anything missing.
  • Debugging — the Fix CI/CD pipeline flow analyzes a failed job, identifies the likely cause, and prepares recommended changes.
  • Cleanup — recommend GitLab-native optimizations like caching, needs: DAGs, and CI/CD components.

GitLab Duo Agentic Chat can pull in context from your issues, merge requests, and pipelines to answer questions in-platform, and Duo Code Suggestions help as you edit .gitlab-ci.yml in the Web IDE.

If GitLab Duo isn't available in your environment, the same conversions work well with a frontier model using a structured prompt. A prompt that produces reliable results:

Convert this GitHub Actions workflow to GitLab CI/CD. Preserve matrix builds, job dependencies, artifacts, and conditional rules. Return a valid .gitlab-ci.yml.

A few guardrails when using any AI assistant for migration: Never paste secrets, tokens, or internal hostnames; always validate generated YAML with GitLab's CI Lint tool; and treat the output as a reviewable draft, not a final commit.

The manual way: A side-by-side example

Whether GitLab Duo drafts the code or you write it by hand, it helps to see the translation. Here's a typical GitHub Actions build-and-test workflow:

      name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '18' }
      - run: npm install
      - run: npm test
      - run: npm run build

    

And the equivalent GitLab CI/CD. Notice checkout disappears (GitLab clones for you), runs-on becomes image, and each step becomes a line in script::

      stages: [test, build]

# GitLab-maintained SAST is one line away
# a nice upgrade to add during migration.
include:
  - template: Jobs/SAST.gitlab-ci.yml

test:
  stage: test
  image: node:18
  script:
    - npm install
    - npm test

build:
  stage: build
  image: node:18
  script:
    - npm install
    - npm run build
  artifacts:
    paths: [dist/]

    

Matrix builds translate just as directly: strategy.matrix becomes parallel.matrix:

      test:
  image: node:${NODE_VERSION}
  parallel:
    matrix:
      - NODE_VERSION: ['16', '18', '20']
  script:
    - npm install
    - npm test

    

And conditional deployments map to rules::

      deploy:
  stage: deploy
  script: ./deploy.sh
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

    

Once you commit .gitlab-ci.yml, GitLab runs the pipeline immediately. To learn more, see the GitLab CI/CD documentation.

What other platforms can GitLab import from?

GitHub isn't the only source. GitLab's importer supports one-click migration from several other platforms:

We also have documentation covering migrations from:


Thanks for reading! Migration doesn't have to be the scary part of adopting a new platform. GitLab moves your data automatically and GitLab Duo handles the CI/CD conversion, so you can focus on shipping. For more, follow the links below: