# 공개 GitHub 저장소를 내 Private 저장소로 완전히 미러 백업하는 방법

> Canonical: https://vxi.me/ko/posts/github-public-repo-private-mirror-backup/
> Locale: ko-kr
> Published: 2026-10-05
> Updated: 2026-10-05
> Markdown: https://vxi.me/ko/posts/github-public-repo-private-mirror-backup.md

공개 GitHub 저장소를 장기간 보관하고 싶고 upstream 저장소가 계속 존재할 것이라고 가정하고 싶지 않다면, 가장 직접적인 방법 중 하나는 **내 계정의 Private 저장소로 완전히 mirror하는 것**입니다.

여기서 말하는 mirror는 일반 `git clone`과 다릅니다. `git clone --mirror`와 `git push --mirror`를 사용해 Git refs를 동기화합니다. 소스 코드 수준의 백업에는 적합하지만 **GitHub 프로젝트 전체를 백업하는 것은 아닙니다.** Issues, Pull Requests, Release 첨부 파일, repository 설정, Secrets 같은 플랫폼 데이터는 자동으로 복사되지 않습니다. Git LFS를 사용하는 저장소라면 LFS object도 별도로 동기화해야 합니다.

> **주의:** `git push --mirror`는 대상 저장소의 refs를 로컬 mirror와 맞춥니다. 대상에만 존재하는 refs를 삭제하거나 같은 이름의 refs를 강제로 업데이트할 수 있습니다. 대상 저장소는 mirror 전용으로 두고 일반 개발 repository와 섞지 않는 편이 안전합니다.

## mirror 백업이 잘 맞는 경우

이 방식은 다음 상황에서 유용합니다.

- 공개 오픈소스 repository에 의존하고 있어 전체 Git history를 직접 보관하고 싶을 때
- upstream이 삭제되거나 이름이 바뀌거나 접근할 수 없게 되는 상황을 대비하고 싶을 때
- upstream branch와 tag를 정기적으로 동기화하되 Fork 협업은 필요하지 않을 때
- 백업 repository를 Private로 유지하고 복구용 사본으로만 쓰고 싶을 때

코드를 수정해 upstream에 PR을 보낼 목적이라면 Fork가 더 적합합니다. 잠깐 코드를 읽는 정도라면 일반 clone이면 충분합니다.

## mirror가 백업하는 것과 하지 않는 것

`git clone --mirror`는 bare mirror repository를 만들고 원격 refs를 추적하도록 설정합니다. 여기에 `git push --mirror`를 사용하면 로컬 mirror의 refs를 다른 Git repository로 동기화할 수 있습니다.

주로 포함되는 것은 다음과 같습니다.

- Git commit history
- branch refs
- tag refs
- 기타 원격에서 보이는 Git refs
- Git objects

반대로 GitHub 플랫폼 계층의 데이터는 자동 복사되지 않습니다.

- Issues
- Pull Requests
- Discussions
- Release 첨부 파일
- Actions Secrets / Variables
- Webhook
- Collaborators / 권한
- Branch protection / Rulesets
- repository visibility 설정

Git LFS 파일은 Git history에 실제 binary 대신 pointer가 저장되므로 LFS object는 별도로 옮겨야 합니다.

## 준비 사항

로컬 환경에 다음 도구를 준비합니다.

- Git
- GitHub CLI(`gh`)
- source repository가 Git LFS를 쓴다면 Git LFS

GitHub CLI 로그인 상태를 확인합니다.

```bash
gh auth status
gh auth setup-git
```

아직 로그인하지 않았다면:

```bash
gh auth login
```

PAT, 비밀번호, 다른 credential을 repository URL이나 script, 문서에 직접 넣지 않는 편이 좋습니다. GitHub CLI credential 설정이나 SSH를 우선 사용합니다.

## 처음 Private mirror repository 만들기

백업하려는 upstream이 다음과 같다고 합시다.

```text
https://github.com/OWNER/REPOSITORY
```

로컬 mirror를 둘 디렉터리에서 다음처럼 실행할 수 있습니다.

```bash
set -euo pipefail

UPSTREAM_URL="https://github.com/OWNER/REPOSITORY.git"
REPO_NAME="REPOSITORY"
ACCOUNT="$(gh api user --jq .login)"
BACKUP_REPO="$ACCOUNT/$REPO_NAME"
BACKUP_URL="https://github.com/$BACKUP_REPO.git"
MIRROR_DIR="$REPO_NAME.git"

if gh repo view "$BACKUP_REPO" >/dev/null 2>&1; then
  echo "Target repository already exists: $BACKUP_REPO"
  exit 1
fi

if [ -e "$MIRROR_DIR" ]; then
  echo "Local path already exists: $MIRROR_DIR"
  exit 1
fi

gh repo create "$BACKUP_REPO" --private

git clone --mirror "$UPSTREAM_URL" "$MIRROR_DIR"

git -C "$MIRROR_DIR" remote add backup "$BACKUP_URL"

git -C "$MIRROR_DIR" push --mirror backup
```

여기에는 유지할 가치가 있는 안전 장치가 두 개 있습니다.

1. **대상 repository가 이미 있으면 즉시 중단합니다.** 기본 동작으로 덮어쓰지 않습니다.
2. **로컬 mirror 디렉터리가 이미 있어도 중단합니다.** 같은 upstream의 mirror인지 먼저 확인해야 합니다.

또 repository의 Private 속성은 GitHub 설정이 결정합니다. Git push 자체가 visibility를 바꾸지는 않습니다.

## 왜 일반 clone + push가 아닌가

일반 working tree clone은 일상 개발에 더 가깝습니다. 현재 branch, remote tracking branch, working directory 파일을 중심으로 동작합니다.

mirror 백업의 목표는 다릅니다. upstream refs를 보존하고 다른 repository가 upstream의 Git 상태와 최대한 같아지도록 만드는 것이 목적입니다.

```bash
git clone --mirror "$UPSTREAM_URL" "$MIRROR_DIR"
```

생성되는 디렉터리는 일반 working tree가 없는 bare repository입니다. 개발에는 불편하지만 동기화와 백업 전용 작업에는 오히려 더 깔끔합니다.

## 이후 증분 업데이트

첫 mirror가 끝난 뒤 매번 다시 clone할 필요는 없습니다.

`REPOSITORY.git`가 있는 상위 디렉터리에서 다음을 실행합니다.

```bash
git -C REPOSITORY.git fetch --prune origin
git -C REPOSITORY.git push --mirror backup
```

여기서:

- `origin`: 공개 upstream repository
- `backup`: 내가 만든 Private mirror repository

`fetch --prune origin`은 최신 refs를 가져오고 upstream에서 사라진 remote refs도 정리합니다. 이어서 `push --mirror backup`이 현재 mirror 상태를 대상에 반영합니다.

mirror를 고정된 디렉터리에 모아 둔다면 absolute path를 사용할 수도 있습니다.

```bash
git -C "$HOME/git-mirrors/REPOSITORY.git" fetch --prune origin
git -C "$HOME/git-mirrors/REPOSITORY.git" push --mirror backup
```

## Git LFS repository는 추가 동기화가 필요하다

Git LFS를 사용하는 project는 Git refs만 동기화해서는 충분하지 않습니다.

먼저 설치 상태를 확인합니다.

```bash
git lfs version
```

mirror 디렉터리에서 LFS object를 동기화합니다.

```bash
git -C REPOSITORY.git lfs fetch --all origin
git -C REPOSITORY.git lfs push --all backup
```

큰 repository에서 `--all`은 시간이 오래 걸릴 수 있고 대상 repository의 LFS storage와 bandwidth quota도 사용합니다.

이후 업데이트는 다음처럼 할 수 있습니다.

```bash
git -C REPOSITORY.git fetch --prune origin
git -C REPOSITORY.git lfs fetch --all origin
git -C REPOSITORY.git lfs push --all backup
git -C REPOSITORY.git push --mirror backup
```

target refs를 업데이트하기 전에 대응하는 LFS object를 최대한 먼저 보내 두는 흐름입니다.

## 백업이 정말 성공했는지 검증하기

먼저 target repository가 Private인지 확인합니다.

```bash
ACCOUNT="$(gh api user --jq .login)"

gh repo view "$ACCOUNT/REPOSITORY"   --json nameWithOwner,visibility,defaultBranchRef
```

그다음 upstream과 backup repository의 현재 refs와 object ID를 비교합니다.

```bash
UPSTREAM_URL="https://github.com/OWNER/REPOSITORY.git"
ACCOUNT="$(gh api user --jq .login)"
BACKUP_URL="https://github.com/$ACCOUNT/REPOSITORY.git"

git ls-remote --refs "$UPSTREAM_URL"   | awk '{print $1, $2}'   | sort > /tmp/upstream-refs.txt

git ls-remote --refs "$BACKUP_URL"   | awk '{print $1, $2}'   | sort > /tmp/backup-refs.txt

diff -u /tmp/upstream-refs.txt /tmp/backup-refs.txt
```

`diff`가 아무것도 출력하지 않고 exit code가 `0`이면 현재 두 repository에서 나열되는 refs와 object ID가 일치한다는 뜻입니다.

LFS project라면 Private repository에서 실제로 clone 또는 checkout도 한 번 해 보는 편이 좋습니다. Git refs만 맞는 것이 아니라 큰 파일도 정상적으로 내려오는지 확인해야 합니다.

## `push --mirror`의 가장 큰 위험

가장 중요한 점은 이것입니다.

> Mirror는 “추가 보관”이 아니라 “상태를 맞추는 작업”입니다.

target repository에 upstream에는 없는 refs가 있다면 `git push --mirror`가 삭제할 수 있습니다. 같은 이름의 refs가 다른 object를 가리키고 있어도 강제 업데이트될 수 있습니다.

그래서 안전한 원칙은 다음과 같습니다.

- upstream repository 하나당 전용 Private mirror repository 하나
- mirror repository에서 별도 feature branch 개발 금지
- mirror repository를 일반 협업 repository로 사용하지 않기
- 첫 동기화 전에 대상이 비어 있는지 확인
- 자동화 전에 수동으로 전체 흐름을 한 번 실행

## 자동화한다면 이렇게 구성한다

여러 공개 repository를 장기간 보관한다면 scheduled task나 CI로 업데이트할 수 있습니다.

```bash
git -C "$MIRROR_DIR" fetch --prune origin

if git -C "$MIRROR_DIR" lfs env >/dev/null 2>&1; then
  git -C "$MIRROR_DIR" lfs fetch --all origin
  git -C "$MIRROR_DIR" lfs push --all backup
fi

git -C "$MIRROR_DIR" push --mirror backup
```

하지만 자동화하기 전에 세 가지 보호 장치를 추가하는 편이 좋습니다.

- 허용된 upstream URL을 명확하게 고정
- push할 backup repository를 명확하게 고정
- 대상 repository가 예상한 값이 아니면 즉시 종료

`push --mirror`를 무인 작업에 넣을 때 중요한 것은 script 몇 줄을 줄이는 것이 아니라 **잘못된 target에 push하지 않게 하는 것**입니다.

## CLI AI에게 실행을 맡길 때는 중단 조건까지 적는다

CLI Agent나 coding assistant에게 대신 실행시키는 경우 “저장소 백업해 줘”라고만 말하기보다 반드시 멈춰야 하는 조건까지 포함하는 편이 안전합니다.

```text
제가 제공한 공개 GitHub 저장소를 현재 로그인한 GitHub 계정의
동일한 이름을 가진 Private 저장소로 완전히 mirror해 주세요.

실행 요구사항:
1. 먼저 git, gh, gh auth status를 확인합니다.
2. 공개 저장소 URL에서 repository 이름을 파싱합니다.
3. 대상에 같은 이름의 repository가 이미 있으면 중단하고 보고합니다. 덮어쓰지 않습니다.
4. 로컬 mirror 디렉터리가 이미 있으면 용도가 확실하지 않은 경우 중단하고 삭제하지 않습니다.
5. 비어 있는 Private repository를 만들고 Fork는 만들지 않습니다.
6. git clone --mirror로 upstream을 clone합니다.
7. backup remote를 추가하고 git push --mirror backup을 실행합니다.
8. Git LFS를 사용하면 LFS object도 동기화합니다.
9. 마지막에 대상 visibility=PRIVATE인지 확인하고 양쪽 git ls-remote --refs를 비교합니다.
10. 이후 증분 업데이트에 사용할 fetch --prune + push --mirror 명령을 알려 줍니다.

안전 경계:
- Token, 비밀번호, Cookie, private key를 출력하거나 요구하지 않습니다.
- 공개 source repository를 수정하지 않습니다.
- 기존 repository나 용도를 모르는 로컬 디렉터리를 삭제하지 않습니다.
- target repository에 기존 내용이 있으면 반드시 중단합니다.
- production 배포나 다른 무관한 작업은 수행하지 않습니다.
```

AI Agent에게 가장 중요한 것은 작업 목적뿐 아니라 **어떤 상황에서 자동으로 진행하지 말고 멈춰야 하는지**를 명확히 하는 것입니다.

## 이 방식의 범위

다시 정리하면 이것은 **Git repository mirror 백업**이지 GitHub 전체 disaster recovery가 아닙니다.

GitHub project 자체를 더 완전하게 보관하려면 별도로 다음 데이터도 고려해야 합니다.

- Issues / Pull Requests export
- Releases와 첨부 파일
- Wiki
- Actions 설정과 변수
- repository 권한과 rules
- Packages / Container images
- Discussions

하지만 주된 목적이 “공개 repository가 나중에 사라져도 전체 source history, branches, tags를 내가 계속 보관한다”라면 `clone --mirror + push --mirror`는 단순하고 투명하며 검증 가능한 방법입니다.

## 공식 참고 자료

- [GitHub Docs: Duplicating a repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/duplicating-a-repository)
- [GitHub CLI: gh repo create](https://cli.github.com/manual/gh_repo_create)
- [Git: git push](https://git-scm.com/docs/git-push)
- [Git LFS: git lfs fetch](https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-fetch.adoc)
- [Git LFS: git lfs push](https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-push.adoc)
