← All case studies GitLab → GitHub

Clear Street spent two and a half quarters moving GitLab to GitHub

Self-hosted GitLab since 2018. Part 1 is the CI pain. Part 2 is dual CI, a tool called Octomigrator, and a mirror that lagged up to 15 minutes.

Clear Street used a self-hosted GitLab instance for code and CI from its founding in 2018. Part 1, 30 June 2022, is why they left and how they designed GitHub Actions. Part 2, 28 September 2023, is the migration. Joseph DeChicchis and Sachin Ananth Navale wrote part 2.

The destination is GitHub Enterprise Cloud, with GitHub Actions and self-hosted runners. The GitLab → GitHub playbook covers repo import, issues, and CI. Clear Street’s hard part was the monorepo called Fleet, and they kept GitLab as the merge source until CI on GitHub matched.

Why GitLab stopped fitting

Part 1 says GitLab was a good all-in-one while the company was small. CI, issues, PyPI, NPM, a Docker registry, and feature flags all lived there. Those pieces later moved out. Artifactory took images and packages. Unleash took feature flags. GitLab was left with code, review, and CI.

About 150 engineers, and growing, worked in Fleet. Push and pull took more than a minute. CI jobs sat in a queue because GitLab’s own queue was slow, even when runners were free. Vertical scale and Sidekiq tuning did not stop the slide. The last log lines of a failed job sometimes never showed up. Large diffs hung or crashed the browser.

They did not want a small team spending its time on self-hosted git servers. About a year before part 1, they decided on GitHub Enterprise Cloud. For CI they looked at CircleCI, GitHub Actions, Codefresh, and Buildkite. Actions won because it sits on GitHub and because of the open-source actions they could reuse.

The spike used actions-runner-controller to run self-hosted runners on their Kubernetes cluster. A sample workflow linted, tested, and built a Go service. They also started writing actions for Clear Street’s own CI.

Three phases, and a quarter on Fleet

Part 2 splits the move into three phases. Spike Actions on a test repo. Move lower-impact repos and build tooling for Fleet. Move Fleet.

The whole migration took two and a half quarters. A full quarter of that was Fleet.

Infrastructure as Code (their Terraform) and Retool went first. Those repos see less traffic, so a CI hiccup hurts less than a hiccup in Fleet. In every migration the first step was a mirror. They used GitLab’s built-in mirror to push code to GitHub. GitLab stayed the source of truth for merges. If GitHub diverged, the mirror overwrote it.

Dual CI, then Octomigrator

The smaller repos had little CI. They checked the new workflows by hand and cut over once they trusted them.

Fleet was too large for that. They built Octomigrator. When someone opened a GitLab merge request, Octomigrator opened a matching GitHub pull request, ran GitHub CI, and commented the result on the GitLab merge request. A job called check_github_workflow_state failed the GitLab pipeline if the GitHub workflow failed. They turned that check on gradually.

They reached a point where GitHub CI was fully up before the cutover. They even deployed artifacts built by GitHub CI while merges still landed on GitLab.

The cutover itself was short. Disable writes to GitLab, enable writes to GitHub, and point git remotes at GitHub. They report few issues after that, on the small repos and on Fleet.

The work was spread across teams

Fleet is a monorepo of team-owned folders. Each team has CI the central infra group does not fully know. They recruited a representative per team to port that CI. Afterward, each team had someone who could answer GitHub questions, so infra was not the only help desk.

They also keep a monorepo of shared actions, plus .github/actions for anything used by one repo. They had used GitLab CI templates. They say Actions compose further, and the public marketplace is larger.

Runner sizes run from small through 2xlarge. The pool is per repository, and every team shares it, so the machines stay generic. A test repo uses the same runner setup as production, and every team could use it during the move.

What the metrics and the survey said

They watch queued jobs, queue time, job duration, runner count, and the Kubernetes nodes under the runners. They alert on long queues and on GitHub incidents.

For a week in August 2023, p90 queue time was about 80 seconds and p95 about 125 seconds. The long tail is off-hours. They scale the runner count down outside the work day, so nightly jobs wait on startup. On a typical day they hit about 1,000 concurrent runners, meaning Kubernetes pods, on about 50 nodes (EC2 instances).

A month after cutover they surveyed engineering. GitHub CI felt more effective than GitLab CI. Git operations felt faster. The UI vote was mixed, and more than half preferred GitHub’s UI. They say everyone reported a positive migration. Written notes asked how to tune notifications and whether stale branches could be pruned. The post shows a chart and does not print the average scores, so I am not inventing them.

What slipped

The mirror lagged from a few minutes to 15 minutes. While CI ran on both sides, that lag slowed the edit loop.

GitHub had performance degradations more often than they expected. They say GitHub usually clears them quickly, and the effect on Clear Street is often small. They link GitHub’s status history for that pattern.

The move sat on a wishlist for more than a year before they staffed it.

What I would copy

Move a quiet repo first, with a mirror that still treats the old host as the only place merges land. Force-overwrite on the new host until cutover, or you will debug two histories.

For a monorepo, run both CI systems and fail the old pipeline when the new one fails. Clear Street’s name for that is Octomigrator plus check_github_workflow_state. Deploy from the new CI before you flip writes.

Put a person from each owning team on the CI port. A central group cannot see every custom job.

Budget for mirror lag. Theirs hit 15 minutes, and that was during the period when people still needed fast feedback.