Why I Left GitLab
In June I moved everything to GitLab and wrote a hundred confident words about why. Last weekend I moved it all back. This is the post that closes the loop.
The June reason
In leaving-github-for-gitlab I wrote the trigger plainly: a visibility scare had me asking why I was still on GitHub at all. And gitlab-superior-for-solo-devs made the case: one URL, one login, integrated CI on every push.
That reasoning was honest. It also had an expiry date I could not see yet.
What expired
| Reason (June) | Status (October) |
|---|---|
| Integrated CI on every push | Pipeline went local — gates run on this machine, remote CI added nothing |
| One login, one home | Code already lived on GitHub — the submodule, the public tools |
| Two hosts add safety | Two hosts meant two drift sources, not safety |
The CI row is the whole story. Once bb gates and the
pre-push hook did the real checking, the hosted pipeline became a green
badge over work done elsewhere. I kept paying the second-host tax —
second login, second token, second drift surface — for a badge.
Prove it, then move it
Nothing switched until both hosts held identical refs. I saved the branch-and-tag list from the old host, pushed branch plus tags to the new private repo, and diffed the two lists until they matched exactly.
Then the stronger proof: a fresh clone from the new host, submodules
included, built green with zero gate failures. Only after that did the
old remote get renamed to a frozen spare and the new one become
origin.
Migration log
Every step below ran once, in order, with output saved. No step ran twice.
| Step | Command | Result |
|---|---|---|
| Record | git ls-remote origin |
main + 3 tags saved |
| Create | gh repo create homepage --private |
PRIVATE, empty |
| Push | git push branch, then tags |
main + 3 tags live |
| Prove | fresh clone, bb doctor, bb build |
0 failures, exit 0 |
| Switch | rename remotes, new origin |
frozen spare kept |
| Retire | archive old repo server-side | private, read-only |
| Rewrites | 7 files, CI file deleted | committed, pushed |
Fewer hops on purpose
Reads were batched into single passes and every green result was accepted immediately — never re-run. The two pushes went out back to back with no inspection round trip between them.
Verification was one pass, not a loop: ref diff, then clone, then build. Three proofs, one direction, zero repeats. Fewer hops is not haste; it is refusing to pay for certainty twice.
The safety switch
The new host notifies Cloudflare on every push, and Cloudflare tries
to build. Its build command is exit 1 — it always fails, so
it can never deploy anything. The red badge is the design, not an
accident.
Real deploys stay on the direct-upload path, and the live stamp check still rules: the served build must equal HEAD, or nothing counts as shipped.
Decisions expire when constraints change — write down the expiry date.