If a client requires their code on Bitbucket while your team collaborates on GitHub, you can run both from a single local repository. This guide walks through adding multiple remotes, pushing to each one selectively, and keeping one host as the canonical source for pull requests while the other stays a mirror.
I set this up recently on a client project where the collaboration and review process happens on GitHub, but the client's internal tooling requires a mirror on Bitbucket. Rather than maintaining two separate clones or relying on a manual copy-paste workflow, I configured one local repo with two remotes and a couple of scripted push commands. This guide documents that exact setup, including the SSH key routing, the branch-naming quirks between hosts, and the commands I actually run day to day.
Why One Repo Can Talk to Several Hosts
Git itself has no concept of a "home" host. Your local .git directory holds the full history, and GitHub, Bitbucket, and GitLab are simply named bookmarks pointing at other copies of that same history. A remote is just a label attached to a URL.
Fetching pulls commits from a remote into your machine. Pushing sends commits from your machine to a remote. The remotes never talk to each other directly, so if you push to GitHub and forget Bitbucket, Bitbucket simply falls behind until you push there too.
The [origin/main] marker tells you which remote branch your local main tracks. That's the target for a plain git pull or git push with no remote name specified.
Adding a Second (or Third) Remote
Create the empty repository on each host first, then point your local project at it.
The convention I use: GitHub stays origin because it's canonical for pull requests and daily pushes, and every other host gets named after itself (bitbucket, gitlab).
Routing SSH Keys per Host
If you use separate accounts across hosts, or you just want clean revocation, generate a dedicated key per host and route it through ~/.ssh/config:
sshconfig
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
Host bitbucket.org
HostName bitbucket.org
User git
IdentityFile ~/.ssh/id_ed25519_bitbucket
IdentitiesOnly yes
Generate a new key and add the public half to the host's UI:
This is the workflow you'll use most. Always name the remote explicitly so there's no ambiguity about where a branch, tag, or deletion is headed.
bash
git push origin main # GitHub only
git push bitbucket main # Bitbucket only
Setting the default upstream
git push -u origin main sets origin as the default target for a plain git push going forward. Run this once for GitHub and skip -u for the mirror, so an unqualified git push never accidentally lands on the wrong host:
bash
git push -u origin main
git push bitbucket main
If the upstream ever gets pointed at the wrong remote, restore it directly:
bash
git branch -u origin/main main
Feature branches, tags, and deletions
Each of these needs the remote spelled out too, since Bitbucket or GitLab won't see anything you don't explicitly push:
bash
git push -u origin feature/i18n # PR branch on GitHub
git push bitbucket feature/i18n # same branch mirrored to Bitbucket
git push origin v1.2.0 # a single tag
git push origin --delete old-branch # remove a branch on one remote only
Hosts don't agree on default branch names, and this catches people off guard the first time they mirror an existing repo.
Host
Common default
GitHub
main
Bitbucket
historically master
GitLab
main (configurable)
Your local branch might be main while an old, empty master branch is still sitting on Bitbucket from when the repo was first created. The Bitbucket UI path /src/master/ will show that stale branch, not the one you're actually pushing to.
Check what a remote actually has:
bash
git ls-remote bitbucket
To make main the canonical branch on a host that still defaults to master, either force master to match main's content (only if the old master history is disposable):
bash
git push bitbucket main:master --force
Or push main on its own and change the host's default branch setting through its UI:
Git can't change a host's default branch over the git protocol itself. That setting lives in the host's own configuration.
First Sync When the Remote Already Has Commits
If the host auto-created a README, license, or .gitignore, your local history and the remote's history may have no common ancestor. Check before pushing:
If the remote's content is disposable, force your history onto it:
bash
git push bitbucket main --force
If you need to keep what's already there, merge the histories explicitly:
bash
git pull bitbucket main --allow-unrelated-histories
git push bitbucket main
Avoid --force on any branch other people rely on unless the team has agreed on it first.
My Daily Workflow for This Setup
Develop and commit locally, same as any single-remote repo.
Open pull requests and do code review on GitHub (origin), which stays canonical.
After a merge, or whenever the client needs the mirror current, sync Bitbucket:
bash
git checkout main
git pull origin main
git push origin main
git push bitbucket main
Confirm the upstream is still GitHub:
bash
git branch -u origin/main main
A Note on CI
Pushing the same commits to two hosts does not duplicate your CI pipelines. GitHub Actions and Bitbucket Pipelines are separate systems, and each one only runs if it's configured on that specific host. If Vercel or another deploy target is bound to GitHub, mirroring to Bitbucket won't trigger a second deployment unless you set that up independently.
FAQ
Does pushing to origin automatically update bitbucket?
No. Remotes never sync with each other. Each git push only reaches the remote you name, so a mirror only stays current if you push to it as a separate step.
What happens if I run git push with no remote name?
It goes to whichever remote your current branch tracks as its upstream, which you can check with git branch -vv. Set this explicitly with git push -u <remote> <branch> so there's no guessing.
Can I set up automatic mirroring so I never have to push twice?
Yes, using a dual push URL on one remote name (git remote set-url --add --push origin <second-url>) or a CI job that mirrors on merge. A shell alias is the simplest version if you're doing this manually.
Why does Bitbucket show master when I've been working in main the whole time?
Bitbucket creates a default branch when the repository is first made, and that's often still master unless changed. Check git ls-remote bitbucket to see exactly which branches exist there, then update the host's default branch setting in its UI.
Is it safe to force-push a mirror branch?
Force-pushing is fine on a mirror that no one else pulls from directly, since you're just overwriting a copy. Treat any branch other people build on as off-limits for force-pushing regardless of which remote it lives on.
Wrapping Up
A single local repository can push to as many hosts as a project needs, and the pattern stays the same regardless of how many remotes you add: name each one explicitly, decide which host is canonical for reviews, and push to the others as deliberate mirroring steps rather than assuming they'll stay in sync on their own. Once the remotes and SSH routing are set up, the daily commands are just git push origin main followed by git push bitbucket main.
Let me know in the comments if you have questions, and subscribe for more practical development guides.