setting them up and keeping them in sync
A mirror repository reproduces a Git repository not just in content but in full structure, including every branch, tag, and internal reference. That fits backups, disaster recovery, and migrations between hosting platforms, but it requires a clear understanding of how git push --mirror actually overwrites a target repository.
Table of Contents
- 1. What mirror repositories are actually used for
- 2. The difference between --bare and --mirror when cloning
- 3. Setting up a mirror repository initially
- 4. Keeping the mirror current with fetch and push
- 5. Automating synchronization with a cron job or a scheduled pipeline
- 6. Force pushes and deleted branches inside the mirror
- 7. A mirror as an intermediate step during a platform migration
- 8. Push mirroring versus each platform's built-in pull mirroring
- 9. Monitoring mirror status and spotting inconsistencies
- 10. Summary
- 11. FAQ
1. What mirror repositories are actually used for
A mirror repository primarily serves as a complete, exact copy of an existing repository, usually on a different server or a different platform entirely. The most obvious use case is a recurring backup that allows a full restore in the event of an outage or data loss on the primary Git server, without relying on individual filesystem snapshots.
A second common use case is migrating between two hosting platforms, for instance from a self hosted GitLab instance to GitHub or the other way around: the mirror keeps both sides in sync during the transition period until the team has fully switched to the new platform. Occasionally, an internal mirror of an externally hosted repository is also required for compliance reasons.
2. The difference between --bare and --mirror when cloning
A clone with --bare produces a repository without a working directory, but by default it only picks up the standard refspec, essentially the branches, not necessarily every internal reference such as notes or every remote tracking branch from the source.
A clone with --mirror goes a step further: it also skips the working tree, but additionally configures an extended refspec automatically that truly mirrors every reference from the source, including all branches, tags, notes, and remote tracking references. For a complete, reliable mirror, --mirror is therefore the right choice, not --bare.
# Create a full mirror clone of the source
git clone --mirror https://git.source.example/project.git
3. Setting up a mirror repository initially
The first step is a mirror clone of the source, which produces a local bare repository holding every reference. Next, a new, empty repository is created on the target platform and registered as an additional remote inside that exact local mirror clone.
Running git push --mirror against that new remote then transfers every reference, meaning every branch and tag, unchanged into the target repository, so the target platform starts out with exactly the same state as the source from the very beginning.
cd project.git
git remote add target https://git.target.example/project.git
git push --mirror target
4. Keeping the mirror current with fetch and push
For ongoing synchronization, a single git remote update is enough, which refreshes every configured refspec of the local mirror clone, picking up new commits as well as new or deleted branches and tags along the way.
Right after that, running git push --mirror against the target remote again transfers this exact updated state completely, so source and target match precisely again after every synchronization run, including any references that were deleted in the meantime.
cd project.git
git remote update
git push --mirror target
5. Automating synchronization with a cron job or a scheduled pipeline
In practice this two step process usually does not run manually, but through a periodic cron job that runs fetch and push one after the other every few minutes or hours, or alternatively through a scheduled CI pipeline calling the same script on its own timetable.
Reliable automation depends on clean error handling: if the fetch step fails, for instance because of a temporary network outage, the following push step should not run at all during that pass, to avoid ever pushing a stale, only partially updated state into the target repository.
#!/bin/bash
set -e
cd /srv/mirrors/project.git
git remote update
git push --mirror target
6. Force pushes and deleted branches inside the mirror
git push --mirror overwrites the target repository to exactly match the current state of the local mirror clone, including any branches and tags deleted in the meantime, which get removed from the target repository too. That is intended behavior for a genuine mirror, but with a misconfigured setup it can just as easily delete real data in the target repository irrecoverably.
For that reason, a target repository fed through a mirror push should never be written to directly by people at the same time. Any manual change on the target side that does not also exist in the source gets lost without a trace on the very next automated synchronization run.
7. A mirror as an intermediate step during a platform migration
During a planned migration, for instance from GitLab to GitHub, a regularly running mirror keeps both platforms in sync throughout the entire transition period, while the team keeps working against the old platform as usual. That considerably reduces the risk of an abrupt cutover, since a complete, current state is always available on the new platform.
The actual switch then happens organizationally rather than technically: once the team is ready, the old platform gets set to read only, one final synchronization run is performed, and every team member's local setup then gets its default remote URL switched over to the new platform.
8. Push mirroring versus each platform's built-in pull mirroring
GitLab offers native pull mirroring in its project settings, where GitLab itself fetches from an external source at configurable intervals, with no need to run a dedicated cron job or pipeline for it. GitHub does not offer a directly comparable built-in feature, but the same fetch and push flow can be reproduced through a scheduled GitHub Action.
The difference from a manual CLI approach mainly comes down to control: native platform mirroring is more convenient but fully depends on that platform's own feature, while a custom script runs independently of either platform involved and can be extended freely with additional checks such as alerting on a failed synchronization.
9. Monitoring mirror status and spotting inconsistencies
A simple but effective check compares the output of git ls-remote for source and target: if the lists of references and their associated commit hashes match, the last synchronization succeeded, otherwise a mismatch points to a failed or incomplete run.
In a production automation setup, such a comparison should also trigger an alert, for instance by email or a chat notification, once two consecutive synchronization runs fail in a row, so a stale mirror never sits unnoticed for an extended period of time.
# Compare the reference lists of source and target
diff <(git ls-remote https://git.source.example/project.git) \
<(git ls-remote https://git.target.example/project.git)
| Criterion | git clone --mirror | git clone --bare | Native platform mirroring |
|---|---|---|---|
| Working tree | No | No | No, managed by the platform |
| Scope of mirrored refs | All refs including notes | Standard refspec only | Depends on the platform |
| Schedule control | Fully self managed, via cron or CI | Fully self managed | Through platform settings, usually limited |
| Platform independent | Yes | Yes | No, tied to the specific platform |
| Fits full backups | Yes, the recommended default | Limited, not every ref included | Limited, depends on feature scope |
Mironsoft
Git workflows, branching strategies, and CI hooks
Chaotic Git history and unclear branching rules across the team?
We set up clean Git workflows, clarify branching strategies for the team, and automate quality checks via Git hooks and CI pipelines so the history stays traceable.
Workflow Audit
Review the existing branching strategy and merge practice for weak spots.
Hook Automation
Set up pre-commit and pre-push hooks for linting, tests, and commit conventions.
Team Training
Teach rebase, cherry-pick, and conflict resolution hands-on across the team.
10. Summary
Git Mirror Repositories
Core idea
git clone --mirror and git push --mirror reproduce a repository exactly on a target server, including every ref.
Key difference
--mirror truly mirrors every reference, --bare only picks up the standard refspec by default.
Typical use
Backups, migrations between hosting platforms, and internal mirrors kept for compliance reasons.
Most important rule
A target repository fed through a mirror push must never be written to manually at the same time.