Migrate from GitHub to GitLab

A planned move from GitHub to self-managed GitLab covers the GitHub importer, user contribution mapping and a separate rewrite of Actions workflows. This page sets out what maps, what does not and what drives effort.

On this page

Migrate from GitHub to GitLab: what the move involves and what it takes

The GitHub importer in GitLab can bring repositories, issues, pull requests and wikis into a self-managed instance, but GitHub Actions workflows are not carried over and must be rewritten as GitLab CI/CD pipelines as a separate piece of work. For a team that needs source control, code review and CI/CD inside infrastructure it controls, the import is the smaller task. The larger effort is usually the pipelines, the access model and the user mapping that follows the import. Before any import, the self-managed instance must be installed, configured and run with backups in place, so the first decision is the install method and the sizing that follows from your user count.

Effort scales with three counts: the number of repositories, the number of users who will contribute history and reviews, and the number of pipelines that must be rebuilt. Repository count and total history size drive import duration and storage, user count drives the placeholder mapping work, and pipeline count drives the rewrite. Ask any partner to size each of these from an inventory of your GitHub organization rather than from a headline number, and confirm which GitHub objects your team actually relies on, such as Actions secrets, environments, branch protection rules and CODEOWNERS files, since these need manual handling. The sizing of the instance itself should follow the published reference architectures for your expected user and API load, and the reference architecture pages are the right next read for that.

Plan the cutover before the first import runs. Decide whether GitHub stays read-only for history, how open pull requests will be handled, and when developers switch their remotes. Confirm the import behavior for your GitHub plan and token scopes in your own environment, because the importer depends on the access granted to it. Also confirm that the instance can reach GitHub from its network, or that you have an approved offline path if your network restricts outbound access. A short pilot on a few representative repositories will show which problems appear, such as large files, mismatched branch names or missing contributor identities, before you commit the full schedule.

Which GitHub capabilities map to GitLab and which need rework

This table pairs each GitHub capability in scope with its closest GitLab equivalent and a migration note on what the importer may not carry over. Read the notes as a checklist for what to verify in your own test import, not as a guarantee of parity. Confirm each importer behavior against the current GitLab import documentation before you plan the cutover.

CapabilityGitLab equivalentMigration note
Repositories and Git historyProjects and repositoriesGit history moves with the importer. Confirm how large repositories, LFS objects and forks are handled in your GitLab version.
IssuesIssuesIssue text, comments and labels are usually imported. Confirm how assignees and closed states map, and whether reactions and linked references survive.
Pull requestsMerge requestsReview content and comments are the main risk. Confirm which discussion threads, review states and commit links the importer keeps.
WikisProject or group wikiWiki content is a separate Git repository. Check that page links and attachments resolve after the move.
Labels and milestonesLabels and milestonesNames and colors usually come across, but check whether group-level labels need to be created first.
ReleasesReleasesRelease notes and tags may need a separate step. Verify attached binaries and asset links after import.
Branch protectionProtected branches and push rulesProtection settings are typically not carried over. Recreate them and test that direct pushes are blocked.
CODEOWNERS-style review rulesCODEOWNERS file and approval rulesGitLab reads a CODEOWNERS file, but approval rule behavior differs. Rebuild required approvals and test them on a sample merge request.
GitHub Actions workflowsGitLab CI/CD pipelines and runnersWorkflows must be rewritten. Treat them as a separate migration with their own runner and secrets plan.
SecretsCI/CD variables and external secrets integrationsSecret values cannot be exported from GitHub. Re-enter them in GitLab and rotate credentials where possible.
Environments and deployment approvalsEnvironments and protected environmentsApproval gates and deployment history are rebuilt, not copied. Confirm the protection model in your tier.
Pages-style static hostingGitLab PagesSites are rebuilt by a CI job, and custom domains and certificates are set up again. Confirm Pages settings for self-managed instances.

Documented import paths from GitHub into self-managed GitLab

GitLab's import documentation lists GitHub as a supported source, but the GitHub importer is only one of several routes. Each path carries a different slice of the work, so the choice follows from what you need to move and how much you want to script.

GitHub importer

The GitHub importer is the documented route for bringing repositories, issues, pull requests and wikis from GitHub into a GitLab group or project. It is the starting point for most teams because it creates the project and maps much of the collaboration history in one pass. Check the import documentation for the exact object types it covers in your GitLab version, and confirm how it handles private repositories and the access token it needs. Plan the user mapping step as a separate task, since contributors are matched or left as placeholders.

Direct repository import by URL

For Git content, GitLab can create a project by pointing it at a repository URL. This moves the Git history and branches but not the GitHub-specific objects such as issues, pull requests and review comments. It suits teams that only need code in GitLab first, or repositories that GitHub's importer cannot reach. Confirm the authentication method for private sources and whether tags and LFS objects are included for your setup before you rely on it for a cutover.

Manifest-based bulk import

A manifest lets you list many Git repositories and import them in one operation rather than project by project. It is useful when a GitHub organization holds dozens of repositories that must land in a planned group structure. The manifest carries repository locations and target paths, not contributor history or collaboration objects. Validate the manifest against a test group first, and confirm the naming and namespace rules that your instance enforces.

File export and re-import

Export-based approaches move a project as a file, which can carry data the live importer does not reach. They depend on the exporting and importing GitLab versions being compatible, so confirm that before you schedule the move. Treat them as a fallback for specific project content rather than a full GitHub migration path, and test the restored result against a sample of issues and merge requests.

API-driven scripting

The GitLab REST API lets you create projects, set memberships, post issues and attach comments when the importer does not cover a object type or your data needs transformation. Scripts give you control over link rewriting, label mapping and retries across many repositories. They also take ongoing engineering effort to write, test and run, so estimate that work before choosing this route. Keep an audit log of every created object so the cutover can be verified and reversed if needed.

Actions workflows are separate

GitHub Actions workflow files are not converted by any of these import paths, so they must be rewritten as GitLab CI/CD pipelines and matched to runners. Repository imports bring the YAML files as ordinary content, which means they will not run until you adapt them. Plan secrets, environments and runner capacity on their own track, and check the sibling migration topic for the rewrite steps.

How the GitHub importer moves repositories, issues and pull requests

The GitHub importer creates one GitLab project for each repository you select, so the first decision is the target namespace and which repositories belong in the first wave. Start by authorizing GitLab to read from GitHub through the import page. Confirm the access token scope and the GitHub organization approval steps for your version before you begin, because organization policies on GitHub can block third-party access even when your account is an owner. Select repositories in batches so that a failed import is easy to trace and repeat.

Issues and pull requests do not arrive as identical objects. Pull requests are expected to appear as GitLab merge requests and issues as GitLab issues, with authors, comments and status carried across in some form. Field-level behavior varies by GitLab version, and the exact mapping for assignees, labels, milestones, reviews and closed or merged states should be checked in the GitLab import documentation for the version you run. Record which fields your team relies on, such as review approvals or linked issues, and test those on one project before importing the rest.

After each import, compare the source and target directly. Run git ls-remote against both the GitHub and GitLab remotes and check that branch and tag names and commit hashes match, including the default branch. Confirm that history is complete by comparing commit counts on key branches, and confirm that Git LFS objects and the wiki came across if your repositories use them. Then sample issue and merge request attachments, because some may remain as links to GitHub-hosted files. Confirm with GitLab whether attachments are copied or referenced on your version, and plan for access to the source if links remain.

Sequencing the move: inventory, pilot, parallel run, cutover and decommission

The sequence moves GitHub work into self-managed GitLab in stages, so each group of repositories and users is proven before the write freeze and GitHub is retired.

  1. Inventory repositories, users, open work and workflows

    Your team lists every repository, wiki, issue, open pull request, GitHub Actions workflow, webhook, deploy key and GitHub App integration, and records which ones are active. Assign an owner to each repository and note which contributors are members of the organization versus external. The output is a signed-off inventory that sets the scope of the import and flags what will not transfer through the GitHub importer, so the team knows which items need manual rebuilding. Confirm the current import behavior against the GitLab import documentation before the inventory is final.

  2. Prepare the GitLab instance and upgrade plan

    Before any import, the platform team installs or confirms the self-managed GitLab instance using an installation method that fits your infrastructure, sets the target edition, and records the version you will run. Plan the upgrade path now, because GitLab requires upgrades to pass through required upgrade stops, so the target version must be reached through the documented stops. Document the backup schedule and restore test for the instance. The output is a running, backed-up GitLab at a known version with a written upgrade path to the version you expect to maintain.

  3. Set up Runners before pipelines move

    The CI/CD team registers GitLab Runners with the executor types your workloads need, such as shell, Docker or autoscaling executors, and keeps Runner major and minor versions aligned with the GitLab version. Runners are isolated from production networks where your policy requires it, and tagged so jobs reach the right fleet. Pipelines are not moved until a test job runs successfully on each Runner pool. The output is a verified Runner fleet with a documented registration and version record.

  4. Import a pilot group and map users

    The team imports a small, representative set of repositories through the GitHub importer, including one with pull requests, issues and a wiki. Review how contributions are attributed, reassign placeholder users where needed, and confirm that access levels match your GitLab groups. Engineers in the pilot group work in the imported projects for a short, agreed period and report gaps. The output is a pilot report listing what transferred, what did not, and the user mapping rules the wider migration will follow.

  5. Run a parallel period with both hosts readable

    Repositories are imported in batches while GitHub stays readable and writable, so teams can compare histories and check that new activity is captured. The team keeps a log of which repositories have been synchronized, and reruns imports or applies manual corrections for any gap found. Pipelines are rebuilt as GitLab CI/CD in this period, but they do not run in production until cutover. The output is a batch-by-batch record showing that each repository has been imported, verified and assigned to a cutover wave.

  6. Freeze writes on GitHub and cut over

    For each cutover wave, the team announces a window, sets the GitHub repositories to read-only, performs a final import of any changes made since the last batch, and confirms the commit counts and branch heads match. Team members update remotes, CI/CD variables and external integrations to point to GitLab, and the first production pipelines run on the GitLab Runner fleet. The output is a verified wave where GitLab is the system of record and GitHub is read-only, with the freeze and remote-change steps recorded.

  7. Decommission GitHub access and retain archives

    After each wave has run stably, the team removes GitHub deploy keys, webhooks, GitHub Apps and personal access tokens that no longer serve a purpose, and archives the GitHub organization according to your records policy. Retain an export or backup that your legal and compliance owners have approved, and confirm the retention period with them. The output is a closed-out GitHub estate with a documented archive location and a list of revoked credentials.

Mapping GitHub contributors to GitLab users without losing attribution

Imported contributions can keep their original authors even when those authors have no GitLab account yet. During a GitHub import, GitLab attaches issues, comments, merge requests and commits to placeholder users, which are inactive accounts created for each GitHub author it cannot match. Once a contributor signs in to GitLab and accepts the reassignment, the placeholder's contributions move to their real account. Until then, the history reads as the placeholder, so the migration plan must say who will claim which contributions and by when. For large migrations, confirm the placeholder limits that apply to your instance and edition with GitLab before the import runs, because a count above the limit leaves some authors unclaimed. The detailed mechanics of placeholder users, reassignment and acceptance are covered in the sibling topic on mapping users and contributions after a GitLab import.

Identity planning decides how many contributors can claim their history cleanly. Start by exporting the list of GitHub authors and comparing their commit emails with the addresses your identity provider holds. GitLab matches accounts by verified email, so contributors who committed with personal or retired addresses will need manual claims. If you use SAML single sign-on, confirm whether your identity provider provisions users through SCIM before the import, since a provisioned account exists when the contributor first signs in and the claim is straightforward. Check which of the authentication methods in the GitLab authentication documentation your organization will enable, and whether self-registration is allowed. Finally, name the owner who approves reassignments, who answers attribution questions after cutover, and who decides how contractors and former employees are handled. Without these owners, placeholder history tends to remain unclaimed for months.

Moving GitHub Actions workflows: what to rebuild and where it runs

Each workflow file under .github/workflows must be rewritten as a .gitlab-ci.yml pipeline or an included file. Triggers, jobs, dependencies, artifacts and conditions map to GitLab keywords, but the syntax and execution model differ, so treat each workflow as a design task rather than a find-and-replace. Start with the workflows that build and test the most important repositories, and record what each one produces before you change it.

  • Triggers and rules: Map on: events to rules: or workflow:rules so merge request and branch pipelines run under the same conditions as before.
  • Stages and dependencies: Group jobs into stages: that reflect real dependencies. Jobs in the same stage run in parallel, and GitLab waits for each stage to finish before starting the next.
  • Artifacts and caches: Rewrite upload and download steps as artifacts: and cache: with explicit paths and expiry times.
  • Matrix builds: Convert strategy matrices to parallel:matrix: and confirm the generated job count matches what the team expects.
  • Container images and tools: Pin the image: for each job and check that tool versions match the images the workflows used.

Before retiring a workflow, run the rewritten pipeline on a branch and compare the artifacts, test reports and deployment results with the original run.

What drives the effort and cost of a GitHub to GitLab move

This table lists the factors that change the effort and cost of moving from GitHub to self-managed GitLab, and whether each one is paid once or recurs. Use it to see which parts of a written quote depend on your own estimates, such as repository count, user mapping and runner design. The main takeaway is that repository history and identity work set most of the one-time effort, while the target's high availability and runner fleet add recurring operating cost.

DriverWhy it changes effort or costOne-time or recurringWhat to ask for in a quote
Repository count and history sizeEach repository is imported and verified, and large histories take longer to transfer and checkOne-timePer-repository inventory, total size, and the import batches and verification steps included
Users and placeholder volumeGitHub contributors who never linked an account become placeholder users that must be reassigned or acceptedOne-timeCount of contributors, expected mapping approach, and who performs reassignment
Open pull requests and issuesOpen items, and their comments and attachments, need a decision on whether to import, close or recreate themOne-timeCounts of open pull requests and issues, and how attachments and review comments are handled
Wikis and project settingsWikis, labels, milestones and settings may need separate handling because the importer scope is limitedOne-timeList of what the importer covers and what is rebuilt by hand
Actions workflows, secrets and environmentsEach workflow is rewritten as GitLab CI/CD, and secrets and environments must be recreated in GitLabOne-time, with recurring upkeep as pipelines changeWorkflow count and complexity, secrets inventory, and whether the rewrite is in scope or separate
Branch protection and approval rulesGitHub rules are mapped to GitLab protected branches and approval settings, and each mapping must be testedOne-time, with recurring policy reviewInventory of rules, the GitLab tier needed for each approval feature, and the test plan
SSO and identity setupConnecting GitLab to your identity provider, group sync and provisioning takes configuration and testingOne-time, with recurring admin effort for user lifecycleIdentity provider, protocols in scope, and whether SCIM provisioning is included
Runner fleet designRunners execute CI/CD jobs, so executor type, scaling and isolation must be chosen and run after cutoverOne-time build, recurring infrastructure and maintenanceExecutor choice, expected job volume, hosting location, and who patches and upgrades runners
High availability or Geo requirementsReference architectures and a secondary site add servers, databases and replication to build and operateOne-time build, recurring infrastructure and operationsTarget user count, reference architecture, and whether a Geo secondary site is in scope

What usually goes wrong in a GitHub to GitLab move and how to prevent it

Most problems in a GitHub to GitLab move come from the gap between what the importer moves and what teams assume it moves. The risks below cover identity, parallel running, secrets, runners and upgrades, and each one has a check you can run before cutover.

Lost attribution from unmapped users

Imported issues, comments and pull requests can arrive under placeholder users if contributors never match a GitLab account. Map every GitHub contributor before the import starts and confirm which accounts are reassigned and which remain placeholders. Review the list of unmatched authors with the people who own the repositories, and decide how to handle former employees and bot accounts. Check the placeholder limits for large migrations before you commit to a schedule.

Pull requests left open on both hosts

When teams keep reviewing on GitHub while the GitLab copy receives merges, the two histories diverge and reviewers lose track of which system is current. Freeze new pull requests on GitHub for each repository at its cutover point and close or redirect them with a link to the GitLab project. Agree on a single merge system per repository and record the date it changes, so the change record shows where code was reviewed.

Pipelines that run twice during parallel run

If both GitHub Actions and GitLab CI/CD build the same commits during a parallel period, deployments and notifications can fire twice and tests can compete for the same environments. Disable deployment jobs in one system before enabling them in the other, and confirm which pipeline owns each branch and environment. Check that scheduled workflows and webhooks are turned off on the retiring side as well as the active side.

Secrets not re-entered

The importer does not carry over GitHub Actions secrets, so pipelines that rely on them fail or, worse, fall back to values stored in code. Inventory every secret and variable each workflow reads, then re-enter them as masked and protected GitLab CI/CD variables at the right group or project level. Rotate credentials that were shared across repositories, and confirm that deploy keys and tokens for external systems are issued again by their owners.

Runners not aligned to GitLab versions

Jobs can fail or behave unexpectedly when GitLab Runner versions drift from the GitLab server version. The Runner documentation advises keeping major and minor versions aligned with GitLab, so pin Runner releases to the same major and minor version as your instance and test a sample pipeline on each executor type. Record which runner fleet serves which projects so that an upgrade of one side does not silently break the other.

Skipped upgrade stops after install

A fresh install on a current release can look finished, yet a later upgrade fails if the instance jumps past a required stop. Teams that install the latest version and plan to update later often discover the gap only when a patch is due. Record the upgrade path from the install version to the target before go-live, and schedule each required stop in your change calendar. Confirm which monthly and patch releases your team will track, since only the current stable line receives bug fixes.

Questions teams ask before moving from GitHub to GitLab

How much does it cost to move from GitHub to GitLab?

The cost has two separate parts: the GitLab subscription and the migration work. The subscription is licensed per user, and the GitLab pricing page lists Free at 0 US dollars, Premium at 29 US dollars per user per month billed annually, and Ultimate as custom priced, with all three available for self-managed installations. The migration work is quoted separately from a written scope. It is driven by the number of repositories and their size, how many pipelines must be rebuilt, the number of users whose contributions need mapping, the hosting design you choose, and how much testing and cutover support you need. Ask any partner for a quote that lists each component, what is one-time and what recurs, such as subscription renewals, runner capacity and ongoing support.

How long does a GitHub to GitLab migration take?

Duration depends mostly on scope and on how many repositories move in each wave. The main drivers are the count and size of repositories, the volume of issues, pull requests and wikis, how cleanly users can be matched between the two systems, and whether the target instance, runners and identity integration already exist. Pipelines usually take longer than repository transfer because Actions workflows are rewritten, not copied. Plan the cutover in waves, validate each wave against a checklist, and agree the timeline in writing before work starts. Ask for a schedule that separates repository import, user mapping, workflow rebuild and cutover, so you can see which step sets the critical path.

What does our engineering team need to provide for the move?

The work needs decisions and access more than heavy effort from your developers. Expect to provide a list of GitHub organizations and repositories in scope, a named owner for each group, and an approved target hostname and hosting location on infrastructure you control. Your team must confirm the GitLab tier, the identity provider used for sign-in, and who approves user mapping. Developers should be available to review imported projects, confirm branch protections and validate a sample of pipelines. Your security team should agree on access rules for administrators and runners before anything is connected. Write these responsibilities into the scope so that delays on either side are visible.

Can our own team run the GitHub to GitLab migration without a partner?

Yes, a capable internal team can run the import, provided it has someone who administers GitLab, someone who understands your GitHub setup, and time to test. The GitHub importer is documented in GitLab's import guidance, so the steps are known. The harder parts are rewriting Actions workflows, mapping users and contributions, rebuilding secrets and runner access, and planning a cutover that avoids double processing. Consider a partner if you have many repositories, tight regulatory evidence requirements, or no one who can own the instance after go-live. Whichever route you take, DeployOpen works on a fixed scope agreed in writing, and ongoing support is available as a retainer.

What does the GitHub importer not bring over to GitLab?

The importer covers repositories, issues, pull requests and wikis from GitHub, but several things must be checked and rebuilt separately. Review the imported result for each project rather than assuming completeness. Items to verify include GitHub Actions workflows, which are not converted automatically, repository secrets and environment variables, branch protection and review rules, webhooks and third-party app connections, and GitHub Packages or release assets. Confirm each item against the current GitLab import documentation for your version, because importer coverage changes between releases. Build an inventory of these items during discovery so nothing is lost at cutover.

How are GitHub Actions workflows handled after the import?

GitHub Actions workflows are moved as a separate project, not as part of the repository import. Each workflow is rewritten as GitLab CI/CD pipeline configuration, with jobs mapped to stages, self-hosted or GitHub-hosted runners replaced by GitLab Runners you operate, and secrets recreated in GitLab. The GitLab Runner documentation describes the executors and autoscaling options you can choose from. Keep pipeline changes in their own branch, run them in parallel with GitHub for a defined period, and compare job results before retiring the old workflows. Reusable workflows, marketplace actions and matrix builds need explicit design decisions. Treat pipeline rebuilds as an engineering task with its own acceptance tests.

How do we map GitHub users and contributions to GitLab accounts?

Imported issues, comments and pull requests initially belong to placeholder users until each person claims or is matched to a GitLab account. Map users before cutover by comparing email addresses and usernames, and decide how unmatched contributors will appear. Confirm that the people who will claim placeholders can sign in through your chosen identity provider, because sign-in determines how accounts are created. Plan for large contributor lists, since the number of placeholders and the approval steps affect how long reassignment takes. Keep a record of mappings as audit evidence for who authored which change.

Should we run GitLab in the cloud, in our data center or air-gapped?

Choose the hosting model before scoping the migration, because it changes the install method, backup design and runner placement. GitLab documents installation through the Linux package, the Helm chart, the GitLab Operator, Docker or from source, and it documents running self-managed instances in offline, air-gapped environments. The number of users and expected CI load determine whether a single node is enough or whether a high availability design is needed. Confirm with GitLab which features your offline or restricted environment supports, and plan upgrades around required upgrade stops from the start.

How DeployOpen scopes and delivers a GitHub to GitLab migration

DeployOpen is a Chainscore Labs company that plans, implements, integrates, hardens, backs up, upgrades, documents and supports self-hosted open-source platforms inside the client's own infrastructure, whether that is their cloud accounts, data centers or air-gapped networks. For a GitHub to GitLab move, that means the work covers the self-managed GitLab instance itself, the import of repositories, issues, pull requests and wikis, the user mapping plan, and the separate rebuild of GitHub Actions workflows. Most engagements are fixed-scope, and the scope is written down before any work starts: which GitHub organizations and repositories move, which GitLab edition and install method is used, how identity is connected, and what evidence is handed over at the end.

The written scope is the basis for the quote. Ask for it to name each deliverable, the environment it lands in, the acceptance checks (for example, a trial import of a representative repository set compared against the source, and a review of the placeholder user list before reassignment), and the dates the client must provide access, approvals and named contacts. Cost is driven mainly by the number of repositories and the amount of history and pull request data, how many users need contribution mapping, how many Actions workflows must be redesigned as GitLab CI/CD, and how much of the surrounding platform (identity, runners, backups) is built during the same engagement. Confirm with DeployOpen which of these items are in the fixed scope and which would be change requests.

Ongoing support, upgrades and continuous testing are available as retainers once the migration is complete. A retainer is the usual route for the recurring work that follows a cutover: applying GitLab minor and patch releases, planning required upgrade stops, reviewing runner fleet health and keeping backups tested. Ask for the retainer terms in writing, including what is covered, how requests are prioritized and how upgrade work is scheduled around the release cadence. DeployOpen does not publish a price list, so the retainer and the one-time migration are both priced from the written scope.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.