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
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 from | Code and history | PRs, MRs, issues | Pipelines | Our estimate per repo + pipeline |
|---|---|---|---|---|
| GitLab (.com or self-managed) | Mirror push, GitHub Importer, or GEI | GEI (gh gl2gh), GA since 3 Aug 2026 | Actions Importer converts GitLab CI | 1 to 3 days |
| Bitbucket Cloud | Mirror push or GitHub Importer | No GitHub tool | Actions Importer converts Pipelines | 1 to 3 days |
| Bitbucket Data Center 5.14+ | Mirror push or GEI | GEI (pull requests only) | Usually Bamboo or Jenkins: Actions Importer converts both | 1 to 3 days |
| Azure DevOps (Git repos) | Mirror push, or GEI from Azure DevOps Services only | GEI (gh ado2gh), pull requests only | Actions Importer converts build and release pipelines | 1 to 4 days |
| Jenkins | Not a repo host | Not applicable | Actions Importer converts Jenkinsfiles in part | 1 to 5 days |
| CodeCommit | Mirror push | No tool | CodePipeline source switches to a GitHub connection | 0.5 to 2 days |
| CircleCI | Not a repo host | Not applicable | Actions Importer converts config.yml | 0.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.
Sources: GitHub migration planning, GitHub Importer, GEI from GitLab, GEI from Bitbucket Server, GEI from Azure DevOps, mannequins, commit attribution.
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.
| What | Mirror push or GitHub Importer | GitHub Enterprise Importer | By hand |
|---|---|---|---|
| Commits, branches, tags | Yes | Yes | Nothing to do |
| Git LFS objects | Mirror: 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 comments | No | GitLab, Bitbucket DC, Azure DevOps | Otherwise keep the old host read-only as an archive |
| Issues and milestones | No | GitLab only | Or keep your issue tracker where it is |
| Wiki | No (it is a separate Git repo you can mirror too) | GitLab only | Mirror the wiki repo |
| CI pipelines | No | No | Actions Importer converts most, you finish the rest |
| Secrets and CI variables | No | No | Always. Rotate rather than copy. |
| Webhooks, deploy keys, integrations | No | No | Always |
| Branch protection and approval rules | No | Azure DevOps repo-level branch policies only | Rebuild as rulesets |
| Users, groups, SSO | No | Activity is attached to placeholder "mannequins" you reclaim | Create 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.
Sources: About GEI, Actions Importer, GitHub Importer, duplicating a repository, large files.
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.
| Tool | What it is | Sources | Moves | Catch |
|---|---|---|---|---|
Mirror push (git clone --bare, git push --mirror) | Plain Git, run from any machine | Any Git host | All 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 Importer | The "Import repository" page on GitHub.com | Any Git repo reachable from the public internet | Source code and commit history | No 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 API | GitLab.com and maintained self-managed versions; Bitbucket Server and Data Center 5.14+; Azure DevOps Cloud; GitHub | History plus collaboration data, depending on source | Documented 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 Importer | gh extension install github/gh-actions-importer, runs in Docker | Azure DevOps, Bamboo, Bitbucket Pipelines, CircleCI, GitLab, Jenkins, Travis CI | Pipeline definitions, converted to workflow YAML | GitHub'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.
Sources: GitHub changelog, 3 Aug 2026, GEI from GitLab, Actions Importer for GitLab, GitLab push mirroring.
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:andonly/exceptconvert to job-levelif: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-onlabels 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.
Sources: GEI from Bitbucket Server, Actions Importer for Bitbucket Pipelines, Atlassian Data Center end of life.
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: 2xsteps don't convert. Map them to a larger runner on purpose; they cost more per minute (see GitHub Actions pricing).oidc: trueis unsupported, andBITBUCKET_STEP_OIDC_TOKENhas 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.
Sources: GEI from Azure DevOps, migration script, TFVC note, Actions Importer for Azure DevOps, Azure DevOps data locations, GitHub data residency.
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.
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
~/.m2or~/.docker/config.jsonsomeone 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
withCredentialsblock 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.
Sources: AWS blog, 24 Nov 2025, CodeCommit document history, AWS migration post, CodePipeline GitHub connections.
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
CodeStarSourceConnectionaction 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.
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,
setupandversionare unsupported. - Caching between jobs of different workflows isn't converted. Expect the first week of builds to be slower until you tune
actions/cachekeys. - The
auditcommand 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.
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:
| Concept | GitLab CI | Bitbucket Pipelines | Jenkins (declarative) | CircleCI | Azure Pipelines | GitHub Actions |
|---|---|---|---|---|---|---|
| File | .gitlab-ci.yml | bitbucket-pipelines.yml | Jenkinsfile | .circleci/config.yml | azure-pipelines.yml | .github/workflows/*.yml |
| Order | stages, needs | steps in order, parallel | stages | workflow requires | stages, dependsOn | needs |
| Machine | image, runner tags | image, runs-on | agent | executor | pool | runs-on, container |
| Branch filter | rules, only/except | pipelines.branches | when { branch } | filters | trigger | on.push.branches, if |
| Secrets | masked variables | secured variables | credentials store | contexts | variable groups | secrets, environment secrets |
| Reuse | include, extends | Pipes | shared libraries | orbs | templates | reusable workflows, composite actions |
| Manual gate | when: manual | trigger: manual | input | type: approval | environment approvals | environment reviewers, or workflow_dispatch |
Sources: GitHub OIDC in AWS, OIDC reference: immutable subject claims, configure-aws-credentials, GitLab OIDC with AWS.
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.
- Add the identity provider in IAM: provider URL
https://token.actions.githubusercontent.com, audiencests.amazonaws.com. One per AWS account; it can sit next to a GitLab or Bitbucket provider during the parallel run. - 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 olderrepo: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 exactsubyour environment jobs send with a staging role before you rely on it. - 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". - Switch the workflow to
permissions: id-token: writeandaws-actions/configure-aws-credentialswithrole-to-assume, as in section 10. - 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.
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 month | Enterprise, $21 per user a month |
|---|---|---|
| Rulesets: required reviews, status checks, no force-push on main | Yes | Yes |
| Required reviewers on deployment environments | No (public repos only) | Yes |
| Environment secrets, deployment branch rules | Yes | Yes |
| Audit log | Yes, in the UI | Yes, plus the audit log API |
| SAML single sign-on | No | Yes |
| Data residency (GHE.com) | No | EU, Australia, US, Japan |
| GitHub's SOC 1 and SOC 2 Type 2 reports | Not listed | Yes, 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.
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.
- 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. - 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. - 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. - 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.
- 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.
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.
| Task | Low, days | High, days |
|---|---|---|
| Inventory and plan | 0.5 | 1 |
| Mirror 4 repositories with history, branches and tags | 0.5 | 1 |
| Translate 3 app pipelines (test, build image, deploy to ECS) | 1.5 | 3 |
| Translate the Terraform pipeline (plan on PR, apply on main) | 1 | 2 |
| OIDC provider and deploy roles in 2 AWS accounts | 0.5 | 1.5 |
| Secrets: list, prune, rotate, re-enter | 0.5 | 1 |
| Rulesets, CODEOWNERS, environments | 0.5 | 1 |
| Users, teams, access review | 0.25 | 0.5 |
| Webhooks and integrations | 0.5 | 1 |
| Shadow run, cutover, rollback drill | 1 | 2 |
| Total | 6.75 | 14 |
| Adds, if they apply | Extra 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 up | 0.5 to 2 |
| Self-hosted runners rebuilt on AWS | 2 to 5 |
| Each Jenkins scripted pipeline or shared library | 1 to 3 |
| Each Azure DevOps release pipeline with gates and approvals | 1 to 2 |
| TFVC to Git conversion | quoted 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.
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.
CI/CD tools for startups
GitHub, GitLab, Bitbucket, Azure DevOps and Jenkins compared for a small team on AWS.
GitHub vs GitLab
Repos, CI, price and self-hosting, head to head.
GitHub Actions pricing
Per-minute rates, free minutes and when AWS runners pay off.
Production readiness checklist
CI/CD, backups, alerts and access, by stage.
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.