OpsRunway Talk to an engineer

GitLab to GitHub migration, plus Bitbucket, Azure DevOps, Jenkins and CodeCommit.

In a GitLab to GitHub migration, or a move from Bitbucket, Azure DevOps, Jenkins or CodeCommit, the code is the easy part: one mirror push carries every commit, branch and tag. The work is everything around it: pull requests and issues, pipelines, stored secrets, webhooks, branch rules and who can log in. Below: what each of GitHub's tools moves, the gotchas for each source, a GitLab CI job translated to Actions, how to swap stored AWS keys for OIDC on the way, a cutover plan with a rollback, and effort numbers for a team of 2 to 15 engineers deploying to AWS.

Checked: 2 Nov 2026, against GitHub, GitLab, Atlassian, Microsoft and AWS documentation and changelogs

01Short answer

Effort figures are our estimates for one repository and pipeline of ordinary size, not measured benchmarks. Section 14 has the full table.

How do you migrate to GitHub with full history?

Mirror the Git history with git push --mirror or GitHub Importer, convert pipelines with GitHub Actions Importer, then rebuild secrets, branch rules and webhooks by hand. GitHub Enterprise Importer can also bring pull requests and issues from GitLab, Bitbucket Data Center and Azure DevOps, but only into GitHub Enterprise Cloud.

Moving fromCode and historyPRs, MRs, issuesPipelinesOur estimate per repo + pipeline
GitLab (.com or self-managed)Mirror push, GitHub Importer, or GEIGEI (gh gl2gh), GA since 3 Aug 2026Actions Importer converts GitLab CI1 to 3 days
Bitbucket CloudMirror push or GitHub ImporterNo GitHub toolActions Importer converts Pipelines1 to 3 days
Bitbucket Data Center 5.14+Mirror push or GEIGEI (pull requests only)Usually Bamboo or Jenkins: Actions Importer converts both1 to 3 days
Azure DevOps (Git repos)Mirror push, or GEI from Azure DevOps Services onlyGEI (gh ado2gh), pull requests onlyActions Importer converts build and release pipelines1 to 4 days
JenkinsNot a repo hostNot applicableActions Importer converts Jenkinsfiles in part1 to 5 days
CodeCommitMirror pushNo toolCodePipeline source switches to a GitHub connection0.5 to 2 days
CircleCINot a repo hostNot applicableActions Importer converts config.yml0.5 to 2 days

If you are still deciding whether to move at all, start with the hub, CI/CD tools for startups, or the head-to-head GitHub vs GitLab. This page assumes you have decided.

What moves to GitHub automatically, and what do you rebuild by hand?

Git history, branches and tags always move. Pull requests and issues move only with GitHub Enterprise Importer, and only from some sources. Pipelines, secrets, webhooks, branch protections and Git LFS objects never move on their own with any of the tools: you convert, copy or recreate each one.

WhatMirror push or GitHub ImporterGitHub Enterprise ImporterBy hand
Commits, branches, tagsYesYesNothing to do
Git LFS objectsMirror: yes, with git lfs push --all. GitHub Importer: no.No. Pointers move, binaries must be pushed after.One extra push per repo
Pull / merge requests and review commentsNoGitLab, Bitbucket DC, Azure DevOpsOtherwise keep the old host read-only as an archive
Issues and milestonesNoGitLab onlyOr keep your issue tracker where it is
WikiNo (it is a separate Git repo you can mirror too)GitLab onlyMirror the wiki repo
CI pipelinesNoNoActions Importer converts most, you finish the rest
Secrets and CI variablesNoNoAlways. Rotate rather than copy.
Webhooks, deploy keys, integrationsNoNoAlways
Branch protection and approval rulesNoAzure DevOps repo-level branch policies onlyRebuild as rulesets
Users, groups, SSONoActivity is attached to placeholder "mannequins" you reclaimCreate teams, invite people, set up SSO

Commits keep their author name and email whatever tool you use. GitHub links a commit to a user by matching the commit's email to an address on their GitHub account, so ask everyone to add their old work email to their account before you migrate.

Comments and reviews that GEI brings over are attributed to mannequins, placeholder identities, until an organisation owner reclaims each one for a real member. By default the member has to accept an emailed invitation before the history becomes theirs.

Which migration tool should you use: mirror push, GitHub Importer, GEI or Actions Importer?

For code alone, a mirror push: it is one command and you control it. Add GitHub Enterprise Importer only if you need pull request and issue history and are on, or moving to, GitHub Enterprise Cloud. Use GitHub Actions Importer for every pipeline, whichever tool moves the code.

ToolWhat it isSourcesMovesCatch
Mirror push (git clone --bare, git push --mirror)Plain Git, run from any machineAny Git hostAll branches and tags. LFS with two extra commands.Nothing but Git. GitHub blocks files over 100 MiB, so history holding one has to be cleaned or moved to LFS first.
GitHub ImporterThe "Import repository" page on GitHub.comAny Git repo reachable from the public internetSource code and commit historyNo LFS objects, no issues or pull requests. A self-managed host behind a VPN is out of reach.
GitHub Enterprise Importer (GEI)GitHub CLI extensions: gh gl2gh, gh bbs2gh, gh ado2gh, plus an APIGitLab.com and maintained self-managed versions; Bitbucket Server and Data Center 5.14+; Azure DevOps Cloud; GitHubHistory plus collaboration data, depending on sourceDocumented only for migrations into GitHub Enterprise Cloud. The docs describe trial runs but no incremental sync, so plan the real run for cutover.
GitHub Actions Importergh extension install github/gh-actions-importer, runs in DockerAzure DevOps, Bamboo, Bitbucket Pipelines, CircleCI, GitLab, Jenkins, Travis CIPipeline definitions, converted to workflow YAMLGitHub's stated goal is 80% conversion per workflow. Secrets, self-hosted runners and unknown plugins are always manual.

The mirror push is the same four commands from any source:

git clone --bare https://gitlab.com/acme/api.git
cd api.git
git lfs fetch --all                               # only if the repo uses LFS
git push --mirror https://github.com/acme/api.git
git lfs push --all https://github.com/acme/api.git  # only if the repo uses LFS

Push into an empty repository. Clone with --bare as above rather than --mirror: a mirror clone also copies the source's internal refs, such as GitLab's refs/merge-requests/*, which you don't want in the new repository. To check the result, compare git ls-remote --heads --tags against the old and the new remote; the two lists should match line for line.

Does GEI need Enterprise Cloud? Every GEI page we read describes migrations "to GitHub Enterprise Cloud (GitHub.com or GHE.com)", and the GitLab changelog names Enterprise Cloud organisations as the destination. We found no page saying a Team-plan organisation can be a target. If you are on Team and want PR history, ask GitHub before you plan around it, or decide that a read-only archive of the old host is enough. For most startups it is.

Actions Importer has four subcommands: audit (what converts across all your pipelines), forecast (expected Actions minutes from past runs), dry-run (writes the YAML locally) and migrate (opens a pull request with the converted workflow). Run audit first; it is the best inventory you will get for free.

How do you migrate from GitLab to GitHub?

Mirror the repositories, or use GitHub Enterprise Importer, the official GitLab to GitHub migration tool, if you want merge requests and issues too. Since 3 August 2026 GEI migrations from GitLab.com and maintained GitLab Self-Managed versions are generally available through the gh gl2gh extension, into Enterprise Cloud organisations on github.com or ghe.com. GitHub Enterprise Server is not a supported target from GitLab.

What GEI brings from GitLab: Git history and the project wiki, commit comments, issues with comments and milestone events, merge requests as pull requests with reviewers, approvers and state events, milestones, releases and release assets, reactions, uploads, and members as mannequins. Merge request comments land as review comments only when diff data is present; otherwise they become flat comments.

What it leaves behind:

  • LFS objects (push them separately), CI/CD pipelines and schedules, CI/CD variables, webhooks, job logs and artifacts.
  • Repository policies: required approvals, merge trains, pipeline gates, mirroring settings. Group settings and membership.
  • Snippets, issue boards, time tracking, design management and vulnerability reports.

Size limits: 40 GiB of Git data per repository (in public preview), 400 MiB per file during migration and 100 MiB after, and a 40 GB limit on the export archive on GitLab.com (self-managed instances may differ).

GitLab gotchas we plan for:

  • Keep GitHub in sync during the parallel run with GitLab's push mirroring. It is available on every GitLab tier, pushes to GitHub with a fine-grained token, and updates within five minutes of each push (one minute if you mirror protected branches only).
  • include: and templates. Actions Importer merges included files into one job graph before converting. That gets a working workflow, but you lose the shared template. Rebuild shared steps as a reusable workflow or composite action in one repository.
  • rules: and only/except convert to job-level if: conditions, partly. Read every converted condition; a wrong one means production deploys from a feature branch.
  • Masked variables must be moved by hand. Use the move to retire the ones that are AWS keys (section 11).
  • GitLab runners with tags become runs-on labels that point at nothing until you set up runners on the GitHub side.
  • Ultimate security scanners (SAST, dependency and container scanning) don't convert, and their findings don't migrate. See section 15 before you move for that reason.

How do you migrate from Bitbucket to GitHub with history?

Full Git history moves with a mirror push from either Bitbucket Cloud or Data Center. Pull request history is the difference: GEI migrates it from Bitbucket Server and Data Center 5.14 and later, while GitHub's documented tools don't cover Bitbucket Cloud pull requests at all.

Bitbucket Data Center users aren't being pushed out. Atlassian's end-of-life plan for Data Center products (read-only from 28 March 2029) names Jira, Jira Service Management, Confluence and Crowd, and says Bitbucket and Bamboo Data Center "will not end of life". So moving off Data Center is a choice, not a deadline.

From Data Center, GEI brings history and pull requests with comments, reviews, file and line comments, required reviewers and attachments. It does not bring branch permissions, commit comments, repository settings, CI pipelines or repositories owned by individual users. LFS objects are pushed afterwards.

From Bitbucket Cloud, mirror the code and keep the Bitbucket workspace read-only as the pull request archive for as long as your auditors or your own curiosity need it. Community scripts exist that recreate pull requests through the API; we haven't vetted them, and recreated PRs lose their review state, so we rarely bother.

Bitbucket Pipelines gotchas (Actions Importer supports audit, forecast, dry-run and migrate for Bitbucket):

  • size: 2x steps don't convert. Map them to a larger runner on purpose; they cost more per minute (see GitHub Actions pricing).
  • oidc: true is unsupported, and BITBUCKET_STEP_OIDC_TOKEN has no mapping. If you already deploy to AWS with Bitbucket's OIDC, add GitHub as a second identity provider in IAM and give it its own roles.
  • Build images from a private ECR repository are unsupported. Log in to ECR in a step first, using OIDC credentials.
  • Secured repository, workspace and deployment variables, and SSH keys, are manual.
  • Pipes don't appear in the importer's table of supported syntax. Plan for each Pipe to become an equivalent action or a few lines of shell, and check them one by one.

How do you migrate from Azure DevOps to GitHub?

Git repositories move with GEI's gh ado2gh extension or a mirror push; pipelines convert with Actions Importer; Azure Boards work items stay behind. GEI reads Azure DevOps Services (the cloud version) only; on Azure DevOps Server, use a mirror push or move to Services first. GEI generates a PowerShell script for a whole organisation, so on Mac or Linux install PowerShell first:

gh ado2gh generate-script --ado-org SOURCE --github-org DESTINATION --output migrate.ps1
./migrate.ps1

From Azure DevOps, GEI moves Git history, pull requests with their user history, work item links and attachments, and repository-level branch policies. That is the whole list: LFS objects, user-scoped and cross-repository branch policies, pipelines, work items (only their links on pull requests move), artifacts, test plans and dashboards stay behind. TFVC repositories have to be moved to Git first; that is its own project.

Pipeline gotchas:

  • Actions Importer converts both build and release pipelines, but service connections, self-hosted agents, environments and pre-deployment approvals are manual.
  • Pre- and post-deployment gates and post-deployment approvals are unsupported. Nested job templates produce invalid YAML and need fixing by hand.
  • Actions Importer needs Azure DevOps Services or Azure DevOps Server 2019 or later.
  • Approvals: Azure DevOps teams often rely on environment approvals. On GitHub, required reviewers on environments in private repositories need Enterprise (section 12). On Team you enforce the same control at the pull request instead.

UK data residency. Azure DevOps lets you host an organisation in the United Kingdom. GitHub's data residency (GHE.com, an Enterprise feature) is offered in the EU, Australia, the US and Japan; the UK isn't listed. If a customer contract names UK storage for source code, read it before you move.

07From Jenkins

Source: Actions Importer for Jenkins.

How do you migrate Jenkins to GitHub Actions?

Run Actions Importer's audit against the Jenkins server, convert the declarative pipelines it can read, and rewrite scripted pipelines and shared libraries by hand. Jenkins hosts no code, so this is purely a CI move; your repositories may already be on GitHub.

What converts: freestyle jobs in part, and Jenkinsfile pipelines with docker and stage fully and agent, environment, steps, triggers and when partly. input, matrix, options and parameters don't. GitHub lists as always manual: scripted pipelines, secrets, self-hosted runners, unknown plugins and mandatory build tools.

Jenkins gotchas:

  • The agent's IAM role. Jenkins on EC2 often deploys with the instance profile of the build machine: every job on it has the same AWS power, and nobody wrote that down. On Actions, each deploy job assumes a narrow role through OIDC instead.
  • Shared libraries (@Library) are Groovy code. They become reusable workflows or composite actions, written by a person.
  • State on the build server. Caches, tools installed by hand, a ~/.m2 or ~/.docker/config.json someone set up years ago. Hosted runners start clean every time; that is where the "mandatory build tools" line bites.
  • Credentials. Nothing copies Jenkins' credential store into GitHub secrets. List every withCredentials block and decide, per credential, whether it can become OIDC.

You can also keep Jenkins and only move the repositories, if the pipelines are many and the Jenkins box is well looked after. We'd still move deploys to Actions with OIDC first, because that is where the security win is.

Do you have to migrate from CodeCommit to GitHub?

No. AWS closed CodeCommit to new customers on 25 July 2024, then reversed that: on 24 November 2025 it returned to full general availability, open to new sign-ups, with pricing and the 99.9% SLA unchanged. The same post put Git LFS support on the roadmap for Q1 2026; CodeCommit's documentation history has no entry for it as of 2 November 2026, so treat LFS on CodeCommit as unconfirmed.

Teams still move because pull requests, code review and CI live better next to each other. If you do, the move is a mirror push:

git clone --bare https://git-codecommit.eu-west-2.amazonaws.com/v1/repos/api api.git
cd api.git
git push --mirror https://github.com/acme/api.git

CodeCommit gotchas:

  • Pull requests and comments don't move. There is no tool. Export what you need for audit evidence before you delete the repository; AWS notes deletion can't be undone.
  • CodePipeline sources. A CodeCommit source action becomes a CodeStarSourceConnection action with the GitHub (via GitHub App) provider. Only a GitHub organisation owner can create the connection, and connections aren't available in a dozen Regions, including Hong Kong, Spain and Zurich. Check yours.
  • IAM-based Git access goes away. CodeCommit access was IAM policy; on GitHub it is teams and repository roles. Re-create the access model deliberately.
  • Approval rule templates and triggers (SNS, Lambda) become rulesets and webhooks or Actions workflows.
09From CircleCI

Source: Actions Importer for CircleCI.

How do you migrate CircleCI to GitHub Actions?

Run Actions Importer against the CircleCI organisation, then finish contexts, orbs and runners by hand. If your code is already on GitHub, this is the smallest move on the page.

  • Jobs, steps, matrices, parameters, triggers and cron triggers convert. Executors map to runs-on.
  • Orbs are partly supported, mapped to actions. Unknown orbs, contexts and project-level environment variables are manual.
  • Self-hosted runner executors, setup and version are unsupported.
  • Caching between jobs of different workflows isn't converted. Expect the first week of builds to be slower until you tune actions/cache keys.
  • The audit command works only with a CircleCI organisation account.

The usual reason to make this move is one bill and one place to look instead of two. Check the minutes maths in our GitHub Actions pricing guide before you assume Actions is cheaper for your build pattern.

10CI translation

Sources: workflow syntax, configure-aws-credentials, actions/checkout, GITHUB_TOKEN permissions, Ubuntu 24.04 runner image, GitLab mapping.

Illustrative. An invented Node API deployed to ECS. Account ID, role and names are placeholders.

How do you convert GitLab CI to GitHub Actions?

Run Actions Importer, then read what it wrote: for ordinary jobs the mapping is close to one-to-one. stages become needs, image becomes container, rules become if. The real change is in the deploy job, where stored AWS keys are replaced by an OIDC role. Here is a typical .gitlab-ci.yml:

stages: [test, deploy]

test:
  stage: test
  image: node:22
  script:
    - npm ci
    - npm test

deploy:
  stage: deploy
  image:
    name: amazon/aws-cli
    entrypoint: [""]
  script:   # AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY are masked CI/CD variables
    - aws ecs update-service --cluster prod --service api --force-new-deployment
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  environment: production

And the same thing as .github/workflows/ci.yml:

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

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    container: node:22
    steps:
      - uses: actions/checkout@v7
      - run: npm ci
      - run: npm test

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production
    permissions:
      id-token: write     # lets the job request an OIDC token
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v6   # pin to a full commit SHA in production
        with:
          role-to-assume: arn:aws:iam::111122223333:role/gha-deploy-prod
          aws-region: eu-west-2
      - run: aws ecs update-service --cluster prod --service api --force-new-deployment

Three details people miss. The amazon/aws-cli image needs its entrypoint cleared on GitLab, which is why the GitLab job looks odd; GitHub's Ubuntu runner has the AWS CLI already. Actions doesn't check out code unless you ask, so every job that needs the repository needs actions/checkout; the deploy job here only calls the AWS API, so it doesn't. And permissions set on a job replace the workflow-level ones, so list contents: read again.

The same concepts for Bitbucket Pipelines, Jenkins, CircleCI and Azure Pipelines, mapped to GitHub Actions:

ConceptGitLab CIBitbucket PipelinesJenkins (declarative)CircleCIAzure PipelinesGitHub Actions
File.gitlab-ci.ymlbitbucket-pipelines.ymlJenkinsfile.circleci/config.ymlazure-pipelines.yml.github/workflows/*.yml
Orderstages, needssteps in order, parallelstagesworkflow requiresstages, dependsOnneeds
Machineimage, runner tagsimage, runs-onagentexecutorpoolruns-on, container
Branch filterrules, only/exceptpipelines.brancheswhen { branch }filterstriggeron.push.branches, if
Secretsmasked variablessecured variablescredentials storecontextsvariable groupssecrets, environment secrets
Reuseinclude, extendsPipesshared librariesorbstemplatesreusable workflows, composite actions
Manual gatewhen: manualtrigger: manualinputtype: approvalenvironment approvalsenvironment reviewers, or workflow_dispatch

How do you replace stored AWS keys with OIDC during the move?

Create GitHub's OIDC identity provider in each AWS account, give each deploy job its own IAM role that trusts only your repository and environment, and delete the old access keys once the rollback window closes. The migration is the cheapest moment to do it, because you are rewriting the deploy jobs anyway.

Not sure where all the stored keys are? Long-lived deploy keys are one of the things an AWS readiness review finds, alongside the rest of IAM.

  1. Add the identity provider in IAM: provider URL https://token.actions.githubusercontent.com, audience sts.amazonaws.com. One per AWS account; it can sit next to a GitLab or Bitbucket provider during the parallel run.
  2. One role per environment, with a trust policy pinned to the repository and the GitHub environment, not to the whole organisation:
    "Condition": {
      "StringEquals": {
        "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
        "token.actions.githubusercontent.com:sub": "repo:acme@111111/api@222222:environment:production"
      }
    }

    The catch for a migration: every repository you create on GitHub during the move is new, and repositories created after 15 July 2026 get an immutable subject claim with the numeric owner and repository IDs: repo:OWNER@OWNER-ID/REPO@REPO-ID:... instead of the older repo:acme/api:.... A trust policy written in the old, name-only form won't match, and the deploy fails at the role assumption. The IDs above are placeholders. GitHub's reference shows the new format for branches; check the exact sub your environment jobs send with a staging role before you rely on it.

  3. Give the role only what the deploy does: push to one ECR repository, update one ECS service, pass one task role. Not AdministratorAccess "for now".
  4. Switch the workflow to permissions: id-token: write and aws-actions/configure-aws-credentials with role-to-assume, as in section 10.
  5. Deactivate, then delete, the old keys. Deactivate the IAM user's keys on cutover day plus one week; delete them a week after that. CloudTrail will show you if anything still used them.

If you were on GitLab with stored keys, note that GitLab CI also supports OIDC to AWS through id_tokens. The keys were a habit, not a platform limit, and the same fix would have worked there.

12SOC 2 controls

Sources: GitHub pricing, environments, rulesets, secure use. Prices checked 2 Nov 2026.

How do you keep secrets, branch rules and SOC 2 evidence intact?

Rebuild the controls on day one, not after the auditor asks. Most of what a SOC 2 auditor wants from source control is on GitHub Team; deploy approvals on private repositories and SAML SSO are Enterprise features.

Control (private repos)Team, $4 per user a monthEnterprise, $21 per user a month
Rulesets: required reviews, status checks, no force-push on mainYesYes
Required reviewers on deployment environmentsNo (public repos only)Yes
Environment secrets, deployment branch rulesYesYes
Audit logYes, in the UIYes, plus the audit log API
SAML single sign-onNoYes
Data residency (GHE.com)NoEU, Australia, US, Japan
GitHub's SOC 1 and SOC 2 Type 2 reportsNot listedYes, annually

On Team, enforce change approval at the pull request: a ruleset on main requiring one approving review and passing checks, a CODEOWNERS file for infrastructure paths, and deploys that run only from main through an environment restricted to that branch. That gives an auditor a reviewed PR for every production change.

Secrets. Don't copy CI variables across one by one. Make the list, delete what is unused, turn AWS keys into OIDC, and rotate the rest as you re-enter them: every old value sat in a system you are about to stop watching. Put production secrets in an environment, not at repository level, so a workflow on a feature branch can't read them.

Runners. GitHub-hosted runners are fresh per job. If you bring self-hosted runners across, make them ephemeral and keep them off public repositories; the pricing guide covers the AWS options and costs. Pin third-party actions to a full commit SHA in deploy workflows.

Changing tools in the middle of a SOC 2 observation period means evidence from two systems. It is allowed, but tell your auditor first. More on the controls in SOC 2 on AWS and DevSecOps for startups.

13Cutover plan

What does a safe cutover to GitHub look like, with a rollback?

Freeze, mirror, switch, run both, then close the way back. Do everything that can run in parallel before the day, keep the freeze to an hour or two, and keep the old system working for a week as the way back.

  1. Inventory and set up.

    List repositories with sizes and LFS use, pipelines (Actions Importer audit), CI variables, webhooks, deploy keys, integrations and everyone with access. Create the GitHub organisation, teams and rulesets. Add the OIDC provider and roles in each AWS account; they are additive and change nothing yet.

  2. Mirror and shadow-run.

    First mirror push, then keep GitHub current with push mirroring or a scheduled mirror job. Convert pipelines with dry-run, fix them, and run them on the mirror with deploys pointed at staging only. Compare test results and build times with the old CI.

  3. Freeze and switch, at a quiet hour.

    Announce it. Make the old repositories read-only. Final mirror push and LFS push; run GEI now if you use it, since it has no incremental sync. Everyone runs git remote set-url origin git@github.com:acme/api.git. Enable production deploys on GitHub, disable them on the old CI. Ship one small change through the new pipeline to production.

  4. Run both.

    GitHub deploys. The old CI keeps running tests on a copy pushed back from GitHub, for comparison and as the rollback path. Its AWS keys are deactivated but not deleted.

  5. Close the way back.

    Archive the old repositories read-only, delete the old AWS keys, remove deploy keys and webhooks, cancel the seats, and keep an export of the old audit log for your evidence folder.

Rollback, decided before the day: if production deploys from GitHub fail and can't be fixed within an agreed time, push GitHub's new commits back to the old host with a mirror push, reactivate the old CI's keys, re-enable its deploy job and unfreeze the old repositories. Code goes back cleanly. Pull requests and comments made on GitHub during the rollback window do not, which is why the window is a week, not a month.

14Effort

Illustrative. An invented team of 6 engineers on GitLab.com: 4 repositories (API, web, worker, Terraform), 4 pipelines, 2 AWS accounts, Slack, Sentry and an issue tracker. Engineer-days are our estimates, not measurements.

How long does a migration to GitHub take a small team?

For the illustrative team below, about 7 to 14 engineer-days of work, spread over three to four weeks of calendar time because of the parallel run. Translating the pipelines is over a third of it; moving the code takes a day at most.

TaskLow, daysHigh, days
Inventory and plan0.51
Mirror 4 repositories with history, branches and tags0.51
Translate 3 app pipelines (test, build image, deploy to ECS)1.53
Translate the Terraform pipeline (plan on PR, apply on main)12
OIDC provider and deploy roles in 2 AWS accounts0.51.5
Secrets: list, prune, rotate, re-enter0.51
Rulesets, CODEOWNERS, environments0.51
Users, teams, access review0.250.5
Webhooks and integrations0.51
Shadow run, cutover, rollback drill12
Total6.7514
Adds, if they applyExtra days
Merge request and issue history with GEI (Enterprise Cloud)1 to 2
Git LFS objects, or history with files over 100 MiB to clean up0.5 to 2
Self-hosted runners rebuilt on AWS2 to 5
Each Jenkins scripted pipeline or shared library1 to 3
Each Azure DevOps release pipeline with gates and approvals1 to 2
TFVC to Git conversionquoted separately

Put your own day rate against the total. For most seed-stage teams the bigger cost is the attention: a week where one engineer thinks about pipelines instead of product.

When should you not migrate to GitHub?

When the reason is fashion and the current setup works. A move costs one to three engineer-weeks and buys you nothing a customer sees. These are the cases where we'd tell you to stay:

  • You're happy on GitLab. Merge requests, CI and runners all work, and nobody complains. GitLab does OIDC to AWS too. Spend the two weeks on the product.
  • You use GitLab Ultimate's security features in earnest. SAST, dependency and container scanning, and vulnerability reports don't migrate. GitHub's equivalents are paid add-ons: Secret Protection at $19 and Code Security at $30 per active committer a month (checked 2 Nov 2026). Price that before you count the seat savings.
  • You self-host for residency or isolation. GEI can't migrate GitLab into GitHub Enterprise Server, and GitHub's residency regions don't include the UK.
  • You're mid-launch or mid-audit. Wait for a quiet month, or the end of the SOC 2 observation period.
  • The only reason is CI cost. Price it first with GitHub Actions pricing; the fix is often in the workflow files, on whatever platform you're on.
  • You're on CodeCommit because you were told it was shutting down. It isn't any more.

Going the other way? GitLab's own GitHub importer brings repository data, branches, issues, pull requests with reviews, comments, wiki pages, release notes, branch protection rules and LFS objects. CI isn't on its list: Actions workflows are rewritten as .gitlab-ci.yml by hand, the same job as this page in reverse.

16Questions

Migrating to GitHub: short answers.

How do I migrate from GitLab to GitHub with full history?

Clone the GitLab project with git clone --bare, then git push --mirror to an empty GitHub repository. That carries every commit, branch and tag. Add git lfs fetch --all and git lfs push --all if the project uses LFS. Merge requests and issues need GitHub Enterprise Importer, which targets GitHub Enterprise Cloud.

Is there an official GitLab to GitHub migration tool?

Yes. Since 3 August 2026 GitHub Enterprise Importer migrates from GitLab.com and maintained GitLab Self-Managed versions through the gh gl2gh CLI extension. It moves history, wiki, issues, merge requests as pull requests, milestones and releases into GitHub Enterprise Cloud. It does not move CI/CD pipelines, variables, webhooks or LFS objects.

Can I migrate Bitbucket to GitHub with history and pull requests?

History, yes, from Bitbucket Cloud or Data Center, with a mirror push or GitHub Importer. Pull requests only from Bitbucket Server or Data Center 5.14 and later, using GitHub Enterprise Importer. For Bitbucket Cloud, GitHub's documented tools don't migrate pull requests, so most teams keep the workspace read-only as an archive.

How do I migrate from Azure DevOps to GitHub?

From Azure DevOps Services, use GitHub Enterprise Importer's gh ado2gh generate-script to migrate Git repositories with pull requests and repo-level branch policies, then GitHub Actions Importer for build and release pipelines. Work items, test plans, artifacts and dashboards stay in Azure DevOps. TFVC repositories must be converted to Git first.

How long does a Jenkins to GitHub Actions migration take?

On our estimates, half a day to a day per simple declarative pipeline, and one to three days per scripted pipeline or shared library, which Actions Importer can't convert. Add time for build tools that were installed by hand on the Jenkins agents, and for replacing the agent's IAM role with per-job OIDC roles.

Is there a converter from GitLab CI, Jenkins, CircleCI or Bitbucket Pipelines to GitHub Actions?

Yes: GitHub Actions Importer, a GitHub CLI extension that runs in Docker. It converts GitLab CI, Jenkins, CircleCI, Bitbucket Pipelines, Azure Pipelines, Bamboo and Travis CI into workflow YAML, and GitHub aims for 80% conversion per workflow. Secrets, self-hosted runners and unknown orbs or plugins are always manual, and Bitbucket's size and oidc options don't convert.

Is AWS CodeCommit being shut down?

No. It was closed to new customers in July 2024, but on 24 November 2025 AWS returned it to full general availability with new sign-ups open. You can stay. If you move to GitHub, it is a mirror push plus switching CodePipeline to a GitHub connection.

Do CI secrets and variables migrate to GitHub?

No tool migrates secret values, from GitLab, Bitbucket, Azure DevOps, Jenkins or CircleCI. Actions Importer lists the secrets each converted workflow needs, and you create them in GitHub. Use the move to delete unused ones, replace AWS access keys with OIDC roles, and rotate the rest. Put production secrets in a GitHub environment so feature branches can't read them.

Will commit authors be preserved?

Yes. Commits keep their author name and email. GitHub links a commit to a user when the email matches an address on their account, so have people add old work emails first. Comments and reviews brought by Enterprise Importer are attached to placeholder "mannequins" until you reclaim them.

Can we keep Jenkins and only move the repositories?

Yes. Jenkins builds from any Git host. It is a reasonable first step if you have many pipelines, but move deploy jobs to Actions with OIDC early, since long-lived AWS credentials on a shared build server are the bigger risk.

Can GitLab and GitHub stay in sync during the migration?

Yes. GitLab's push mirroring, available on every tier, pushes to a GitHub repository using a fine-grained token and updates within five minutes of each push, or one minute if you mirror protected branches only. Use it for the parallel run, treat the GitHub copy as read-only until cutover day so changes flow one way, then turn the mirror off.

Can I migrate a self-hosted GitLab behind a VPN to GitHub?

Yes. A mirror push works from any machine that can reach both the GitLab server and GitHub, so a VPN is no obstacle. GitHub Importer can't help here, because it needs the source reachable from the public internet. GitHub Enterprise Importer supports maintained GitLab Self-Managed versions, but only into GitHub Enterprise Cloud, not GitHub Enterprise Server.

17Related
18Next step

If you have one or two repositories and a simple pipeline, you don't need us: the mirror push and Actions Importer above will get you there in a few days. If you have several pipelines, AWS keys stored in CI, or an auditor watching, we run the move as a fixed-scope job: inventory, translated workflows, OIDC roles, the cutover and the rollback, quoted per job after a short call. Tell us what you run today. If CI is one of several things nobody owns, a fractional DevOps plan covers it: Essentials gives you up to 20 hours a month of a senior engineer, from $2,900 a month.

StripPLAN-20
PlanEssentials
Hoursup to 20 / mo
ResponseNext business day
TermMonthly after 3 mo
Price$2,900 /mo
NextFractional DevOps plan
Talk to an engineer