GitLab imports your repositories, reviews and history almost completely. It imports none of your GitHub Actions. What moves, what you rewrite, and when.
What Moves on Its Own
GitLab has a built-in GitHub importer, and it is better than most people expect. Point it at a repository with a classic personal access token carrying the repo scope, and it brings across:
- All branches, plus the fork branches behind open pull requests
- Git data and Git LFS objects
- Issues, pull requests and their comments
- Issue and pull request events
- Labels, milestones and release notes
- Branch protection rules
- Pull request reviews, review comments and review suggestions
- Collaborators, with their roles mapped to GitLab roles
- Wiki pages
- Attachments in comments, descriptions and release notes
- Assigned reviewers and merged-by information
Two of those need a footnote. Attachments are an opt-in checkbox, off by default so imports run faster, and issue and pull request events are likewise imported only as an additional item.
Pull requests become merge requests. Reviews survive. The discussion under a three-year-old bug survives. For the repository itself this is close to a complete move, and it is why teams come away from the first test import feeling good about the whole project.
That feeling is the trap.
What Does Not Move
GitLab's own documentation is clear about the omissions, and one of them is the entire reason your repository is useful.
GitHub Actions workflows do not transfer. Not partially, not with a warning. The importer has nothing to say about them, because GitLab CI/CD is a different system with a different execution model.
The rest of the list is smaller but worth knowing before you promise a date:
- Secrets. GitHub Actions secrets are recreated by hand as GitLab CI/CD variables, and you mark them masked and protected yourself before a pipeline runs
- GitHub organizations and groups
- "Require status checks to pass before merging" rules, even though other branch protection does come across
- Custom repository roles from GitHub Enterprise Cloud
- Outside collaborators
- Markdown attachments from GitHub Enterprise Server instances, except images and video, which do import on GitLab 18.4 and later
- Image attachments uploaded to private repositories before 2023-05-09
- Attachments where the source organization uses SAML single sign-on, which GitLab says "might fail"
- Threading on pull request diff comments created before 2017. The comments themselves import, but each one lands in its own thread, because GitHub's API omits the reply information for comments that old
The CI Rewrite Is the Project
Everything above is an afternoon. The pipelines are the quarter.
GitLab publishes a concept mapping, and it is honest about being a mapping rather than a translation:
| GitHub Actions | GitLab CI/CD |
|---|---|
env | variables |
jobs | stages |
on | Not applicable |
run | Not applicable |
runs-on | tags |
steps | script |
uses | include |
Read the right-hand column carefully. Two of the seven rows have no equivalent at all, and the docs explain where those concepts actually go. Triggers are implicit in Git events, with rules deciding which jobs run, and each run command becomes one entry in a job's script array, the same keyword that absorbs steps.
That is not a formatting quirk. GitHub composes a job out of steps, where each step can be a published action with its own inputs, outputs and versioning. GitLab composes a job out of a shell script. A workflow that is ten uses: lines pulling ten community actions has nothing to import, because on GitLab those ten things become either shell commands you write, a template you include, or a container image you build.
There are two conversion paths and neither is a converter in the sense most people mean.
GitLab Duo, their AI assistant, can translate GitHub Actions workflows into GitLab CI/CD, and their own migration post presents this as the fast way against doing it by hand. Separately there is a free agent skill that does the same job, which the marketing page calls "the hardest step of any migration, solved".
The project page for the same thing names it "Agent skill: GitHub Actions to GitLab CI/CD workflow translator (prototype)". The repository was created in May 2026 and carried 28 commits and no tags at the time of writing.
What both have in common matters more than the difference between them. They are language models reading your workflow and writing YAML, so the output depends on the model, and two runs can produce two different pipelines. That makes either one a useful first draft and a poor basis for an estimate. Treat what comes out the way you would treat a junior engineer's first attempt, which is to say review every line of it.
It is also worth noticing what GitLab's own migration post, the one titled the easy way, still lists as manual. Secrets. Which is to say the two things most likely to stop a pipeline in production, the logic and the credentials, are both on you.
The docs are equally plain about the limits of the mapping itself, noting that the setup-terraform action and the Terraform.gitlab-ci.yml template are not exact matches.
So the honest estimate for a team with real pipelines is that most repositories move in an afternoon, the biggest one overnight, and the CI takes weeks. How many weeks depends almost entirely on how much of your workflow logic lives in third-party actions rather than in your own scripts.
If your pipelines are mostly run: blocks calling your own make targets, you are in good shape. If they are a marketplace assembly, you are rewriting rather than migrating. Our note on building CI/CD pipelines with GitHub Actions is a reasonable inventory of the patterns that will and will not survive the crossing.
When the Move Is Worth It
Four reasons hold up.
You need it on your own hardware, under your own rules. GitLab Self-Managed is a first-class product rather than a lesser edition, and for a team with data residency obligations or an air-gapped requirement that is often the whole argument. GitHub Enterprise Server exists too, so this is a comparison rather than a knockout, but GitLab's self-hosted path is the better travelled one.
You want the security scanning in the platform rather than bolted on. SAST, DAST, dependency scanning and container scanning are GitLab features rather than a marketplace of separate tools you wire together and then keep wired together. Check which tier you would actually need before you count this as a reason. GitLab splits these between basic scans that run broadly and the enterprise-grade scanning, the vulnerability report and the findings shown inside merge requests, which sit higher up the range. That line moves as vendors reshuffle their tiers, so read the current comparison rather than an article about it, this one included. If you are maintaining that wiring yourself today, moving it into the platform removes a class of work.
You are already one repository per service and drowning in configuration. GitLab's include and CI/CD components let one team own a pipeline definition that forty projects consume. GitHub has reusable workflows, but the ergonomics of include at scale are genuinely better.
You want the registry, the packages and the Kubernetes agent from the same vendor as the repository. Fewer integrations, fewer tokens, fewer things to rotate.
When It Is Not Worth It
Three reasons to stay, and none of them is weak.
Your pipelines are the marketplace. If you counted your uses: lines and it is a large number, you are proposing to rewrite your delivery system in order to change where your Git history is stored. That is a bad trade unless one of the four reasons above applies with real force.
You are an open-source project. Contributors have GitHub accounts. Discoverability, forks and drive-by pull requests all live there. Moving costs you contribution volume, and no feature comparison compensates for that.
Your team is fluent in one system and nobody is asking to change. Retraining is a real line item that never appears in the migration plan and always appears in the next quarter's velocity.
How to Actually Do It
The order matters more than the tooling.
- Inventory the pipelines before you touch a repository. Count the
uses:lines across every workflow file and sort them into three piles: our own scripts, an action that is really a shell command, and an action doing something genuinely complicated. The third pile is your project plan. - Prove the importer on your worst repository, not your simplest. Pick the one with the most history, the oldest comments and the private image attachments. If that survives, the rest will.
- Check the prerequisites. You need Maintainer or Owner on the destination GitLab group, access to the source project, a classic token with
repo, andread:orgas well if you want collaborators and LFS. The GitHub organization must not be restricting third-party application access, which is a setting somebody turned on years ago and nobody remembers. - Rewrite one pipeline end to end before importing anything else. One service, from commit to production, on GitLab. This is where you find out whether your estimate was fiction.
- Run both for a while. Keep GitHub as the source of truth until the GitLab pipeline has deployed to production more than once and somebody has rolled back on it. A migration nobody has rolled back has not been tested.
- Move the rest, then remove the old path deliberately. Archive rather than delete, and keep the old tokens alive until you are certain nothing automated is still calling them.
What to Expect on the Clock
GitLab's documentation includes a useful reality check. Importing the Kubernetes repository, with roughly 80,000 pull requests, 45,000 issues and about 1.5 million comments, took 76 hours.
Your repositories are smaller than Kubernetes. The number is still worth carrying, because it tells you the import is bounded by API calls against GitHub rather than by your network or your patience, and that a large repository is an overnight job rather than a coffee break. Plan the cutover window around the biggest one, not the average.
For the opposite direction, GitHub made its GitLab importer generally available in August 2026, which we covered in GitHub now migrates GitLab repositories in one command. It is worth reading alongside this one, because it has the same shape. The repository is the easy half and the delivery system is the half that costs money.
The Short Version
The repository move is close to solved. GitLab brings across more than you expect, including the review history teams assume they will lose.
The delivery system does not move at all. The tools that claim to translate it are a paid AI assistant and a prototype skill, both of which write a draft rather than perform a conversion, and your secrets are recreated by hand either way. On most teams that delivery system is where the value and the complexity actually live. Decide the migration on that basis. If your pipelines are your own scripts, this is a week. If your pipelines are an assembly of other people's actions, this is a rewrite wearing a migration's clothes, and it deserves to be planned and budgeted as one.
If you want a second opinion on which of those two you have, we set up and migrate CI/CD pipelines for a living, and the inventory in step one is usually enough to tell.
Or read how we handle it in CI/CD Pipeline Setup.
Related Articles
How to Do Canary Releases Without a Service Mesh
You can send five percent of production traffic at a new version, watch the error rate, and roll back in seconds without installing a service mesh. This walks through replica weighted canaries with a progressive delivery controller, real percentage splitting at the edge, an automated pass or fail check against Prometheus, and the rollback path. It also covers what changed when Kubernetes retired Ingress NGINX in March 2026.
Server & DevOpsHow to Troubleshoot and Fix the "Error Establishing a Database Connection" in WordPress
An in-depth troubleshooting guide for the WordPress database connection error, covering wp-config.php verification, password resets, database repair, MySQL service management, and preventive measures.
Server & DevOpsMySQL Master-Slave Replication Setup Guide (Source-Replica Syntax for 8.4)
MySQL 8.4 removed the master-slave syntax entirely, so CHANGE MASTER TO and SHOW SLAVE STATUS now fail outright. This guide uses the current source-replica syntax that works on both 8.0 and 8.4, with a full mapping table, GTID setup, HAProxy read distribution, and the two things that actually break in production: Seconds_Behind_Source lying to you, and reading your own writes.