Mirroring Your Code to GitHub and GitLab Simultaneously
Context
In a professional environment, relying on a single Git hosting provider introduces availability, policy, and vendor-lock risks. In my workflows, I adopted a multi-platform Git strategy to ensure redundancy, portability, and CI/CD continuity across providers.
This document captures a real-world implementation, including edge cases and failure modes encountered during setup.
The Objective
To configure a single local Git repository to push to both GitHub and GitLab using one command (git push origin main), ensuring redundancy and synchronized CI/CD across platforms. Moving from a single-cloud setup to a "Multi-Cloud Git" setup is a rite of passage for many developers. This tutorial shows how to do that, including avoiding tricky roadblocks.
The Setup
Step 1: Establish your "Origin"
First, ensure your primary remote (usually GitHub) is set up.
Example:
zettelkasten on main
❯ git remote add origin git@github.com:hemroda/zettelkasten.git
error: remote origin already exists.
Step 2: The "Stacking" Technique
To push to two places, you don't add a new remote name; you add a push URL to the existing one.
In our example:
zettelkasten on main
❯ git remote set-url --add --push origin git@gitlab.com:hemroda/zettelkasten.git
The "First" Gotcha: When you add a push URL for the first time, Git stops using the default "fetch" URL for pushing. Indeed if you check the remote URLs, you'll see only the push URL for GitLab:
zettelkasten on main
❯ git remote -v
origin git@github.com:hemroda/zettelkasten.git (fetch)
origin git@gitlab.com:hemroda/zettelkasten.git (push)
You must explicitly re-add your GitHub URL to the push list so both are present:
Now, if you check the remote URLs again, you'll see both GitHub and GitLab URLs:
zettelkasten on main
❯ git remote -v
origin git@github.com:hemroda/zettelkasten.git (fetch)
origin git@gitlab.com:hemroda/zettelkasten.git (push)
origin git@github.com:hemroda/zettelkasten.git (push)
Step 3: Configure Authentication (The SSH Shortcut)
Using SSH for both is the smoothest experience. To add your key to GitLab quickly on macOS:
- Copy your key:
pbcopy < ~/.ssh/id_ed25519.pub - Paste it into GitLab Settings > SSH Keys.
- Test it:
ssh -T git@gitlab.com
Step 4: Resolving the "Diverged History" Conflict
When you try to push to GitLab, you might encounter a "Diverged History" conflict.
zettelkasten on main
❯ git push
To gitlab.com:hemroda/zettelkasten.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'gitlab.com:hemroda/zettelkasten.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Everything up-to-date
If you created your GitLab repo with a README or .gitignore, or other files, it has a history your local machine doesn't know about. A standard push will be rejected.
The Fix:
- Fetch the GitLab-specific data:
- Merge it into your local branch:
(The --allow-unrelated-histories flag is the secret sauce here; it forces Git to combine two projects that didn't start from the same commit.)
Summary of "Gotchas"
| Gotcha | Why it happens | The Fix |
|---|---|---|
| The "Replacement" Bug | Adding one push URL often hides the default one. | Always verify with git remote -v and re-add the primary URL. |
| Protocol Mismatch | Using HTTPS for one and SSH for the other causes inconsistent login prompts. | Stick to SSH for both for a "silent" push experience. |
| Push Rejected | GitLab often has an initial commit (README) that your local Git doesn't have. | Use fetch followed by merge --allow-unrelated-histories. |
The Final Result
Now, your workflow is simply:
The terminal will show two separate progress bars—one for GitHub and one for GitLab.