Multiple Git Remotes
A remote is a named pointer to another copy of a repository — nothing
more. origin is only a convention: the name git clone gives the remote it
cloned from. A repo can have any number of remotes with any names, and that’s
how one local repo pushes to both a self-hosted GitLab and GitHub.
your working copy
├── origin → ssh://git@gitlab.example.com/team/project.git (primary)
└── github → https://github.com/you/project.git (mirror)
The commands
git remote -v # list remotes and their URLs
git remote add github <url> # add a second remote named "github"
git push github master # push a branch to a specific remote
git remote rename origin gitlab # names are yours to choose
git remote remove github # detach (never touches the remote repo)
Fetch and push URLs can even differ per remote (git remote set-url --push),
though that’s rarely needed.
Upstream tracking — where a bare git push goes
Every local branch can have one upstream (tracking branch): the
remote/branch pair that plain git push, git pull, and git status’s
“ahead/behind” compare against. With multiple remotes this is the detail that
bites:
git branch -vv # shows each branch's upstream, e.g. [origin/master]
git status -sb # first line: ## master...origin/master
git branch -u origin/master # set (or fix) the upstream explicitly
A branch tracks exactly one upstream — pushing to the other remote is
always an explicit git push <remote> <branch>.
Creating the second remote with a CLI (gh, glab)
The hosting CLIs can create the server-side repo and wire the remote in one step. GitHub’s:
gh repo create <name> --private --source . --remote github --push
--source .— use the current repo instead of creating a fresh one--remote github— add the new repo as a remote with this name--push— push the current branch immediately
The trap: --push also re-points the current branch’s upstream to the
new remote. From then on, a bare git push goes to GitHub, not to the remote
you think of as primary — silently. If the original remote is the primary,
restore it right after:
git branch -u origin/master
GitLab’s glab repo create behaves analogously for self-hosted GitLab.
Visibility: private vs public vs internal
Repo visibility is a property of the server-side repo, set at creation
(--private / --public; GitLab adds --internal — visible to anyone
logged into that GitLab instance, which on a company or self-hosted server is
not the same as private). Check, don’t assume:
gh repo view <owner>/<name> --json visibility
A safe habit: default to private and open up deliberately — you can flip private → public in one setting, but nothing un-publishes a repo that was cloned while public.
Keeping the mirror current
Two remotes never sync themselves; every update is an explicit push:
git push origin master && git push github master
For a repo where the mirror must never lag, that pair can live in a small script or a git alias. Accept that the mirror lags otherwise, and treat the primary remote as the source of truth.
[!note] In your repos (internal — remove this section before sharing) House convention (global CLAUDE.md, established 2026-07-23 with
nat-demo): the self-hosted GitLab is alwaysoriginand keeps upstream tracking; GitHub (unixtool1192) is a second remote namedgithub, and repos pushed there are private by default — this work is distributed only by Tony, for TD SYNNEX. The--pushupstream-stealing trap in this note was hit live during the nat-demo push and fixed withgit branch -u origin/master.