Setting Up Git Mirror Repositories and Keeping Them in Sync
AI generated
git
HEAD
Git
Git Mirror Repositories
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.

9 min read Git Backup

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.

11. FAQ: Git Mirror Repositories

1What is the most important difference between --mirror and --bare when cloning?
A --mirror clone truly mirrors every reference of the source including notes and remote tracking branches, while --bare only picks up the standard refspec, essentially the branches, by default.
2Do deleted branches also get removed from the target on the next sync run?
Yes, git push --mirror overwrites the target repository to exactly match the current state of the local mirror clone, so deleted references get removed from the target too.
3Can the target repository also be written to manually in parallel?
That is strongly discouraged, since any manual change on the target that does not also exist in the source gets overwritten without a trace on the next automated synchronization run.
4How often should a mirror repository be synchronized?
That depends on the use case, a pure backup usually works fine with an hourly or daily run, an active migration phase benefits from a much shorter interval of just a few minutes.
5Does GitLab offer a built-in alternative to a manual mirror script?
Yes, GitLab offers native pull mirroring in its project settings, where the platform itself fetches from an external source at configurable intervals.
6How do I know whether a mirror run actually succeeded?
Comparing the output of git ls-remote for source and target shows directly whether both sides carry the same references with the same commit hashes.
7What happens if the fetch step fails due to a network error?
With clean error handling in the automation script, the following push step should not run at all during that pass, to avoid pushing an incomplete state into the target.
8Does a mirror work as a substitute for a regular database backup?
No, a Git mirror only secures the content of the repository itself, a production system still needs separate backups for every other data source involved.
9Can I mirror one source into several target repositories at once?
Yes, it is enough to register several remotes in the local mirror clone and run the push step for each target remote one after another inside the same automation script.
10Is a mirror repository by itself already a complete backup?
For the plain code content, yes, but accompanying platform data such as issues, pull request discussions, or CI configuration should be backed up separately if needed, since none of that is part of the plain Git references.