Docker Volume을 GitHub에 자동 백업하기: 1Panel용 저비용 복구 구성
직접 Docker 서비스를 운영할 때 가장 쉽게 미루는 일이 백업입니다. container가 잘 돌아가고 있으면 “이건 나중에 해도 되겠지”라고 생각하기 쉽습니다. 실제 장애가 생기면 어려운 것은 container를 다시 띄우는 일이 아니라 volume 안의 데이터, 1Panel 설정, 직접 바꾼 파일을 복원할 수 있느냐입니다.
저는 결국 꽤 절제된 구성을 선택했습니다. 무거운 백업 시스템을 처음부터 만들지 않고 object storage도 바로 붙이지 않았습니다. 매일 Docker volume과 1Panel 설정을 한 번 압축하고, 로컬에는 최근 7일만 남긴 뒤 압축 파일을 GitHub private repository에 동기화합니다.
화려한 구성은 아니지만 두 가지를 먼저 해결합니다. 최근 며칠 데이터는 빠르게 복구할 수 있고, 현재 서버 자체가 망가져도 원격에 한 벌이 남습니다.
Docker 자동 백업, Docker volume GitHub 백업, 1Panel 백업 같은 구성을 찾고 있다면 이 글의 범위가 거의 그대로 맞습니다.
처음부터 무거운 백업 시스템을 만들지 않은 이유
백업이라고 하면 object storage, 버전 아카이브, 지역이 다른 여러 사본, 전용 백업 플랫폼부터 떠올리기 쉽습니다. 방향은 맞지만 개인 서버나 작은 팀의 머신에서는 “기능이 부족해서”보다 매일 안정적으로 실행되는 백업 자체가 없어서 문제가 되는 경우가 더 많습니다.
제가 먼저 중요하게 본 것은 세 가지였습니다.
- 사람이 기억해서 실행하지 않아도 자동으로 돌아갈 것
- 장애가 나면 최근 며칠의 데이터를 빠르게 복구할 수 있을 것
- 백업이 현재 서버 한 대에만 남지 않을 것
그래서 전체 흐름을 다음 정도로 단순하게 만들었습니다.
Docker volume / 1Panel 설정
↓
docker-sync 스크립트
↓
로컬에 backup_<timestamp> 생성
↓
7일보다 오래된 백업 삭제
↓
압축 파일을 Git repository 디렉터리로 복사
↓
Git LFS로 .tar.gz 추적
↓
GitHub private repository로 push
대용량 백업에는 적합하지 않고 엄밀한 enterprise disaster recovery도 아닙니다. 하지만 1Panel에서 몇 개의 사이트, 데이터베이스, 서비스를 돌리는 정도라면 로컬 7일 + GitHub private repository 조합은 꽤 실용적입니다.
먼저 GitHub SSH 연결을 수동으로 확인한다
이 단계는 앞에서 먼저 하는 편이 좋고, 반드시 한 번은 직접 검증하는 것을 권합니다. 나중에 Shell script, 1Panel scheduled task, 다른 자동화를 사용하더라도 결국 중요한 것은 이 서버가 GitHub에 push할 수 있는가이기 때문입니다.
서버에서 먼저 실행합니다.
ssh-keygen -t ed25519 -C "[email protected]"
cat ~/.ssh/id_ed25519.pub
ssh -T [email protected]
첫 번째 명령은 SSH key를 만들고, 두 번째로 public key를 복사해 GitHub SSH keys에 등록합니다. 세 번째로 실제 연결을 확인합니다.
이 단계는 생략하지 않는 편이 좋습니다. 자동 작업이 처음부터 실패하는 원인이 script가 아니라 SSH 인증인 경우가 많습니다. 먼저 신뢰 관계를 확인하면 이후 디버깅이 훨씬 단순해집니다.
백업용 repository는 서비스 소스 코드와 분리하는 편이 좋습니다. 예를 들면 다음과 같습니다.
[email protected]:kuangyongjie/my-docker-backup.git
그리고 repository는 Private로 두는 것이 기본입니다.
전체 흐름을 하나의 docker-sync 명령으로 고정한다
백업 로직을 임시 script 폴더 어딘가에 두면 몇 달 뒤 “어디에 있었지, 어떻게 실행했지?”부터 다시 찾게 됩니다. 장기적으로 유지할 작업은 entry point를 고정하는 편이 좋습니다.
그래서 저는 시스템 명령 하나로 묶었습니다.
/usr/local/bin/docker-sync
직접 실행할 때도, 1Panel에서 호출할 때도 같은 명령을 사용합니다. 핵심 script는 다음과 같습니다.
cat << 'EOF' > /usr/local/bin/docker-sync
#!/bin/bash
GITHUB_REPO="[email protected]:kuangyongjie/my-docker-backup.git"
GIT_USER_NAME="kuangyongjie"
GIT_USER_EMAIL="[email protected]"
LOCAL_SYNC_PATH="/root/github_sync_space"
if ! command -v git-lfs &> /dev/null; then
apt-get update && apt-get install -y git git-lfs && git lfs install
fi
if [ ! -d "$LOCAL_SYNC_PATH/.git" ]; then
echo "--- 第一次运行:初始化同步目录 ---"
rm -rf "$LOCAL_SYNC_PATH"
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git clone "$GITHUB_REPO" "$LOCAL_SYNC_PATH"
cd "$LOCAL_SYNC_PATH"
git lfs track "*.tar.gz" && git add .gitattributes
git config user.name "$GIT_USER_NAME" && git config user.email "$GIT_USER_EMAIL"
git commit -m "Init LFS"
else
cd "$LOCAL_SYNC_PATH" && git pull origin main --rebase
fi
TAG=$(date +%Y%m%d_%H%M%S)
DIR="/root/docker_backups/backup_$TAG"
mkdir -p "$DIR"
echo "--- 正在抓取本地数据 ---"
for vol in $(docker volume ls -q); do
tar -zcf "$DIR/vol_$vol.tar.gz" -C $(docker volume inspect $vol --format '{{.Mountpoint}}') .
done
tar -zcf "$DIR/1panel_apps_config.tar.gz" /opt/1panel/apps
echo "--- 正在清理本地 7 天前的旧备份 ---"
find /root/docker_backups/ -maxdepth 1 -name "backup_*" -type d -mtime +7 -exec rm -rf {} +
echo "--- 正在同步到 GitHub ---"
cp "$DIR"/*.tar.gz "$LOCAL_SYNC_PATH/"
cd "$LOCAL_SYNC_PATH"
git add .
git commit -m "Auto Backup $TAG"
git push origin main
echo "--- 完美!所有任务已完成 ---"
EOF
chmod +x /usr/local/bin/docker-sync
긴 스크립트처럼 보여도 실제로 하는 일은 여섯 가지뿐이다
이 script의 흐름은 단순합니다.
git-lfs가 없으면 설치- 첫 실행이면 백업 repository를 clone하고 LFS 초기화
- Docker volume을 순회하며 각 volume을
tar.gz로 압축 /opt/1panel/apps설정도 함께 압축- 7일보다 오래된 로컬 백업 디렉터리 삭제
- 압축 파일을 sync 디렉터리로 복사한 뒤 commit / push
무거운 의존성을 추가하지 않는 것이 장점입니다. Shell, Git, Git LFS, Docker volume 경로처럼 이미 서버에 있거나 쉽게 설치할 수 있는 도구만 사용합니다. 나중에 문제가 생겨도 어느 단계에서 깨졌는지 비교적 쉽게 볼 수 있습니다.
오히려 신경 써야 할 것은 다음 두 가지입니다.
- 새 파일 변경이 없으면
git commit이 실패 코드로 끝날 수 있음 docker volume ls -q는 머신의 모든 volume을 대상으로 하므로 어떤 volume이 있는지 알고 있어야 함
이 구성을 더 다듬는다면 먼저 이 두 부분을 보완하는 것이 좋습니다.
1Panel에는 긴 스크립트를 넣지 않고 호출만 맡긴다
긴 백업 script를 1Panel scheduled task 안에 그대로 붙여 넣는 방식은 권하지 않습니다. 처음에는 편해 보여도 나중에 repository 주소, 보관 일수, 사용자 정보를 바꿀 때 유지보수가 어려워집니다.
더 단순한 흐름은 다음과 같습니다.
- CLI에서
docker-sync를 직접 실행해 정상 동작 확인 - 1Panel에서 scheduled task 생성
- task 유형은 Shell script
- 하루 한 번, 예를 들어 새벽
03:00 - script 내용은 한 줄만 사용
docker-sync
어느 날 백업이 실패해도 SSH로 접속한 뒤 docker-sync를 바로 실행하면 로그를 확인할 수 있습니다. panel 안의 긴 script부터 찾을 필요가 없습니다.
실제로는 백업보다 복구 절차가 더 중요하다
많은 백업 글이 “파일을 저장했다”에서 끝납니다. 하지만 사고가 났을 때 필요한 것은 백업 구조의 우아함이 아니라 어떻게 복구할지입니다.
서버가 망가졌다면 대략 다음 흐름으로 복구합니다.
- 새 서버에서 백업 repository clone
- 원하는 시점의 압축 파일 선택
- 해당
vol_xxx.tar.gz압축 해제 docker volume create로 대상 volume 생성- 해당 volume의
Mountpoint에 데이터 복원 - container를 다시 만들고 volume mount
1Panel 설정도 비슷합니다. 설정 디렉터리를 먼저 되찾은 뒤 새 실행 환경에 맞게 다시 연결합니다.
복구 현장에서 더 어려운 것은 압축 해제 방법이 아니라 어떤 volume이 어떤 서비스에 해당하는지 알고 있느냐입니다. 파일은 있는데 서비스와 volume의 관계를 모르면 복구 시간은 여전히 길어집니다. 평소에 mapping을 기록해 두는 편이 좋습니다.
자주 확인하는 명령
| 목적 | 명령 |
|---|---|
| 즉시 수동 백업 | docker-sync |
| 로컬 백업 용량 확인 | du -sh /root/docker_backups |
| 로컬 백업 목록 확인 | ls -lh /root/docker_backups |
| 최근 동기화 기록 | cd /root/github_sync_space && git log -n 5 |
| 로컬 보관 일수 변경 | nano /usr/local/bin/docker-sync |
7일을 14일 또는 3일로 바꾸고 싶다면 find ... -mtime +7 부분만 조정하면 됩니다. script 전체를 다시 설계할 필요는 없습니다.
자주 실패하는 지점
압축 명령보다 다음 부분에서 더 자주 문제가 생깁니다.
- 서버와 GitHub 사이 SSH trust가 완성되지 않음
git-lfs가 없거나 초기화되지 않음- 로컬 sync 디렉터리의 첫 초기화 권한이 잘못됨
- scheduled task는 실행됐지만 새 파일이 없어
git commit이 실패
또 현실적인 한계도 있습니다. GitHub private repository는 Docker 백업의 두 번째 사본으로 사용할 수 있지만, 설정 파일이나 작은 database volume, 일반적인 애플리케이션 데이터처럼 크기가 관리 가능한 경우에 더 적합합니다. 데이터가 계속 커진다면 결국 object storage나 전용 백업 매체로 옮기는 편이 낫습니다.
그래서 이 구성의 위치를 정확히 잡는 것이 중요합니다. “최종 enterprise DR 시스템”이 아니라 1Panel / Docker 서버에 안정적으로 자동 실행되는 복구 경로를 먼저 만드는 방법입니다.