Ghost 블로그 백업 자동화 완벽 가이드
Ghost 블로그의 MySQL, 콘텐츠를 cron과 GitHub으로 자동 백업하는 시스템 구축 가이드입니다.
1. 왜 백업이 중요한가
셀프 호스팅에서 백업은 선택이 아닌 필수입니다. 다음 상황에서 데이터를 보호합니다:
- 서버 장애: 클라우드도 완벽하지 않음
- 해킹/랜섬웨어: 복원 가능한 백업이 최후의 보루
- 실수: 게시물 삭제, 잘못된 설정 변경
- 업그레이드 실패: 롤백 필요 시
Ghost(Pro)는 자동 백업이 포함되지만, 셀프 호스팅은 직접 구축해야 합니다.
2. 백업 전략 설계
2.1 백업 대상
| 대상 | 중요도 | 방법 |
|---|---|---|
| MySQL 데이터베이스 | ⭐⭐⭐ | mysqldump |
| Ghost 콘텐츠 (이미지, 테마) | ⭐⭐⭐ | tar.gz |
| docker-compose.yml | ⭐⭐ | 파일 복사 |
2.2 3-2-1 백업 원칙
3-2-1 백업 원칙:
├── 3개의 복사본
├── 2개의 다른 저장 매체
└── 1개는 오프사이트 (서버 외부)
이 가이드의 구성:
| 저장소 | 위치 | 보관 기간 |
|---|---|---|
| 서버 로컬 | ~/backup/ | 7일 |
| GitHub Private Repo | 클라우드 | 무제한 (최신 1개) |
2.3 왜 GitHub인가?
| 저장소 | 무료 용량 | 장점 | 단점 |
|---|---|---|---|
| GitHub Private | 무제한 | 무료, 버전 관리, 간편 | 단일 파일 100MB 제한 |
| Cloudflare R2 | 10GB | S3 호환 | 설정 복잡 |
| Google Drive | 15GB | 용량 큼 | API 설정 필요 |
| Backblaze B2 | 10GB | 저렴 | 유료 전환 시 비용 |
Ghost 블로그의 백업 파일은 보통 수십 MB 수준이므로 GitHub이 가장 간편합니다.
3. 백업 스크립트 작성
3.1 스크립트 디렉토리 생성
mkdir -p ~/scripts ~/backup
3.2 backup.sh 작성
cat > ~/scripts/backup.sh << 'EOF'
#!/bin/bash
# Ghost 블로그 백업 스크립트 (GitHub 연동)
set -e
# === 설정 ===
BACKUP_DIR="$HOME/backup"
GITHUB_REPO="$HOME/backup-repo"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
# MySQL 정보 (docker-compose.yml과 동일하게 설정)
MYSQL_USER="ghost"
MYSQL_PASSWORD="YOUR_GHOST_DB_PASSWORD" # ← 실제 비밀번호로 변경
MYSQL_DATABASE="ghost"
# 색상 출력
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
echo -e "${GREEN}=== Ghost 백업 시작 [$(date)] ===${NC}"
# 1. MySQL 데이터베이스 백업
echo -e "${YELLOW}[1/4] MySQL 데이터베이스 백업 중...${NC}"
docker exec ghost-db mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE 2>/dev/null > "$BACKUP_DIR/ghost-db-$DATE.sql"
echo " → ghost-db-$DATE.sql 생성 완료"
# 2. Ghost 콘텐츠 볼륨 백업
echo -e "${YELLOW}[2/4] Ghost 콘텐츠 백업 중...${NC}"
docker run --rm \
-v ghost_ghost-content:/data:ro \
-v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/ghost-content-$DATE.tar.gz" -C /data .
echo " → ghost-content-$DATE.tar.gz 생성 완료"
# 3. Docker Compose 설정 복사
echo -e "${YELLOW}[3/4] Docker 설정 백업 중...${NC}"
cp ~/docker/ghost/docker-compose.yml "$BACKUP_DIR/docker-compose-$DATE.yml"
echo " → docker-compose-$DATE.yml 생성 완료"
# 4. GitHub에 Push
echo -e "${YELLOW}[4/4] GitHub에 백업 업로드 중...${NC}"
if [ -d "$GITHUB_REPO/.git" ]; then
cd "$GITHUB_REPO"
# 기존 파일 삭제 (최신 백업만 유지)
rm -f "$GITHUB_REPO"/ghost-db-*.sql
rm -f "$GITHUB_REPO"/ghost-content-*.tar.gz
rm -f "$GITHUB_REPO"/docker-compose-*.yml
# 새 백업 복사
cp "$BACKUP_DIR/ghost-db-$DATE.sql" "$GITHUB_REPO/"
cp "$BACKUP_DIR/ghost-content-$DATE.tar.gz" "$GITHUB_REPO/"
cp "$BACKUP_DIR/docker-compose-$DATE.yml" "$GITHUB_REPO/"
# Git commit & push
git add -A
git commit -m "Backup: $DATE" || echo "No changes to commit"
git push origin main
echo " → GitHub push 완료"
else
echo -e "${RED} → GitHub repo 미설정, 로컬 백업만 진행${NC}"
fi
# 5. 오래된 로컬 백업 삭제
echo -e "${YELLOW}[정리] ${RETENTION_DAYS}일 이상 된 로컬 백업 삭제 중...${NC}"
find "$BACKUP_DIR" -name "ghost-*" -mtime +$RETENTION_DAYS -delete 2>/dev/null || true
find "$BACKUP_DIR" -name "docker-compose-*" -mtime +$RETENTION_DAYS -delete 2>/dev/null || true
# 6. 결과 출력
echo -e "${GREEN}=== 백업 완료 ===${NC}"
echo "로컬 백업: $BACKUP_DIR"
ls -lh "$BACKUP_DIR" | grep -E "(ghost-|docker-compose-).*$DATE" || true
EOF
3.3 실행 권한 부여
chmod +x ~/scripts/backup.sh
3.4 테스트 실행
~/scripts/backup.sh
정상 출력:
=== Ghost 백업 시작 [Sun Jan 18 22:45:45 KST 2026] ===
[1/4] MySQL 데이터베이스 백업 중...
→ ghost-db-20260118_224545.sql 생성 완료
[2/4] Ghost 콘텐츠 백업 중...
→ ghost-content-20260118_224545.tar.gz 생성 완료
[3/4] Docker 설정 백업 중...
→ docker-compose-20260118_224545.yml 생성 완료
[4/4] GitHub에 백업 업로드 중...
→ GitHub repo 미설정, 로컬 백업만 진행
[정리] 7일 이상 된 로컬 백업 삭제 중...
=== 백업 완료 ===
4. GitHub 백업 저장소 설정
4.1 GitHub Private Repo 생성
- GitHub 접속
- New repository 클릭
- 설정:
Repository name: ghost-backup
Visibility: Private # ← 중요!
Initialize: (체크 안 함)
- Create repository 클릭
4.2 서버에서 SSH 키 생성 (없는 경우)
# SSH 키 생성
ssh-keygen -t ed25519 -C "server-backup"
# 공개키 확인
cat ~/.ssh/id_ed25519.pub
4.3 GitHub에 SSH 키 등록
방법 1: Deploy Key (권장)
- GitHub repo → Settings → Deploy keys
- Add deploy key
- Title:
backup-server - Key: 공개키 붙여넣기
- Allow write access ✅
- Add key
방법 2: 계정 SSH Key
- GitHub → Settings → SSH and GPG keys
- New SSH key
- Key 붙여넣기
4.4 서버에서 Git 설정
# Git 사용자 설정
git config --global user.name "backup-bot"
git config --global user.email "[email protected]"
# SSH 연결 테스트
ssh -T [email protected]
# Hi username! You've successfully authenticated...
4.5 Repo 클론
git clone [email protected]:YOUR_USERNAME/ghost-backup.git ~/backup-repo
4.6 백업 스크립트 재실행
~/scripts/backup.sh
이제 GitHub에 백업이 push됩니다:
[4/4] GitHub에 백업 업로드 중...
[main abc1234] Backup: 20260118_224545
3 files changed, 2616 insertions(+)
→ GitHub push 완료
5. Cron 자동화 설정
5.1 서버 시간대 확인/설정
# 현재 시간대 확인
timedatectl
# 한국 시간대로 변경 (선택)
sudo timedatectl set-timezone Asia/Seoul
5.2 Cron Job 등록
# crontab 편집
crontab -e
다음 줄 추가:
# Ghost 백업 - 매일 새벽 1시 (KST)
0 1 * * * /home/ubuntu/scripts/backup.sh >> /home/ubuntu/backup/backup.log 2>&1
Cron 표현식 설명:
0 1 * * *
│ │ │ │ │
│ │ │ │ └── 요일 (0-7, 0과 7은 일요일)
│ │ │ └──── 월 (1-12)
│ │ └────── 일 (1-31)
│ └──────── 시 (0-23)
└────────── 분 (0-59)
5.3 Cron 설정 확인
crontab -l
출력:
0 1 * * * /home/ubuntu/scripts/backup.sh >> /home/ubuntu/backup/backup.log 2>&1
5.4 로그 확인
백업 실행 후 로그 확인:
tail -50 ~/backup/backup.log
6. 백업 복원 방법
6.1 MySQL 복원
# 백업 파일로 복원
docker exec -i ghost-db mysql -u ghost -pYOUR_PASSWORD ghost < ~/backup/ghost-db-20260118_224545.sql
6.2 Ghost 콘텐츠 복원
# 기존 볼륨 삭제 (주의!)
docker compose down
docker volume rm ghost_ghost-content
# 새 볼륨에 복원
docker volume create ghost_ghost-content
docker run --rm \
-v ghost_ghost-content:/data \
-v ~/backup:/backup \
alpine tar xzf /backup/ghost-content-20260118_224545.tar.gz -C /data
# 컨테이너 재시작
docker compose up -d
6.3 전체 복원 스크립트
cat > ~/scripts/restore.sh << 'EOF'
#!/bin/bash
# Ghost 복원 스크립트
set -e
BACKUP_DATE=$1
if [ -z "$BACKUP_DATE" ]; then
echo "Usage: $0 <BACKUP_DATE>"
echo "Example: $0 20260118_224545"
exit 1
fi
BACKUP_DIR="$HOME/backup"
DB_FILE="$BACKUP_DIR/ghost-db-$BACKUP_DATE.sql"
CONTENT_FILE="$BACKUP_DIR/ghost-content-$BACKUP_DATE.tar.gz"
# 파일 존재 확인
if [ ! -f "$DB_FILE" ] || [ ! -f "$CONTENT_FILE" ]; then
echo "Error: Backup files not found for $BACKUP_DATE"
exit 1
fi
echo "=== Ghost 복원 시작 ==="
# 1. 컨테이너 중지
cd ~/docker/ghost
docker compose down
# 2. MySQL 복원
echo "[1/3] MySQL 복원 중..."
docker compose up -d ghost-db
sleep 10 # DB 시작 대기
docker exec -i ghost-db mysql -u ghost -pYOUR_PASSWORD ghost < "$DB_FILE"
# 3. 콘텐츠 복원
echo "[2/3] 콘텐츠 복원 중..."
docker volume rm ghost_ghost-content 2>/dev/null || true
docker volume create ghost_ghost-content
docker run --rm \
-v ghost_ghost-content:/data \
-v "$BACKUP_DIR":/backup \
alpine tar xzf "/backup/ghost-content-$BACKUP_DATE.tar.gz" -C /data
# 4. 컨테이너 시작
echo "[3/3] 컨테이너 시작 중..."
docker compose up -d
echo "=== 복원 완료 ==="
EOF
chmod +x ~/scripts/restore.sh
사용법:
~/scripts/restore.sh 20260118_224545
7. 모니터링 및 알림 (선택)
7.1 백업 성공 여부 확인 스크립트
cat > ~/scripts/check-backup.sh << 'EOF'
#!/bin/bash
# 백업 상태 확인
TODAY=$(date +%Y%m%d)
BACKUP_DIR="$HOME/backup"
# 오늘 백업 파일 확인
DB_FILE=$(ls -1 "$BACKUP_DIR"/ghost-db-${TODAY}*.sql 2>/dev/null | tail -1)
CONTENT_FILE=$(ls -1 "$BACKUP_DIR"/ghost-content-${TODAY}*.tar.gz 2>/dev/null | tail -1)
if [ -n "$DB_FILE" ] && [ -n "$CONTENT_FILE" ]; then
echo "✅ 오늘 백업 성공"
echo " DB: $(basename $DB_FILE) ($(du -h $DB_FILE | cut -f1))"
echo " Content: $(basename $CONTENT_FILE) ($(du -h $CONTENT_FILE | cut -f1))"
else
echo "❌ 오늘 백업 없음!"
exit 1
fi
EOF
chmod +x ~/scripts/check-backup.sh
7.2 Discord/Slack 알림 (선택)
# backup.sh 끝에 추가
WEBHOOK_URL="https://discord.com/api/webhooks/xxx/yyy"
curl -H "Content-Type: application/json" \
-d "{\"content\": \"✅ Ghost 백업 완료: $DATE\"}" \
"$WEBHOOK_URL"
8. 트러블슈팅
8.1 mysqldump 권한 오류
에러:
mysqldump: Error: 'Access denied; you need the PROCESS privilege'
해결: 이 경고는 무시해도 됩니다. 실제 데이터는 정상 백업됩니다.
8.2 Docker 볼륨 이름 확인
docker volume ls | grep ghost
볼륨 이름이 다르면 스크립트 수정 필요:
# 예: ghost-ghost-content 대신 myproject_ghost-content인 경우
-v myproject_ghost-content:/data:ro
8.3 GitHub Push 실패
# SSH 연결 테스트
ssh -T [email protected]
# 권한 확인 (Deploy Key에 Write 권한 있는지)
# GitHub repo → Settings → Deploy keys → Allow write access ✅
8.4 Cron이 실행 안 됨
# Cron 서비스 상태 확인
sudo systemctl status cron
# Cron 로그 확인
grep CRON /var/log/syslog | tail -20
9. 핵심 개념 정리
| 개념 | 설명 |
|---|---|
| Docker 볼륨 | 컨테이너 데이터 영구 저장소 |
| mysqldump | MySQL 데이터베이스 백업 도구 |
| Cron | Linux 작업 스케줄러 |
| Deploy Key | 특정 repo만 접근 가능한 SSH 키 |
| 3-2-1 백업 | 3개 복사본, 2개 매체, 1개 오프사이트 |
10. 베스트 프랙티스
체크리스트
- [ ] 백업 스크립트 테스트 실행
- [ ] GitHub repo Private 설정
- [ ] Deploy Key에 Write 권한 부여
- [ ] Cron 로그 정기 확인
- [ ] 복원 테스트 (연 1회 이상)
권장 백업 주기
| 상황 | 주기 |
|---|---|
| 활발한 블로그 | 매일 |
| 가끔 포스팅 | 주 1회 |
| 거의 업데이트 없음 | 월 1회 |
11. FAQ
Q: 백업 파일 크기는 얼마나 되나요?
A: 콘텐츠 양에 따라 다르지만, 일반적인 블로그는 DB 수백 KB ~ 수 MB, 콘텐츠(이미지 포함) 수십 MB ~ 수백 MB입니다.
Q: GitHub 100MB 파일 제한에 걸리면?
A: Git LFS를 사용하거나, 이미지를 Cloudflare R2 같은 외부 스토리지로 분리하세요.
Q: 서버가 죽으면 복원할 수 있나요?
A: GitHub에 백업이 있다면 새 서버에서 Docker 설치 후 복원 가능합니다.
Q: 로컬 백업만으로 충분한가요?
A: 서버 자체에 문제가 생기면 로컬 백업도 함께 사라집니다. 오프사이트 백업(GitHub)을 권장합니다.
12. 다음 단계
백업 자동화까지 완료했습니다! 다음으로 검색엔진에 블로그를 등록합니다.
시리즈 목차:
- Oracle Cloud 무료 서버 세팅
- Ghost 블로그 Docker 설치
- Ghost 블로그 백업 자동화 ← 현재 글
- 검색엔진 등록 (Google/Naver)
- Ghost 6.0 업그레이드
- Ghost ActivityPub 설정 (Fediverse)
- Ghost Analytics 설정 (Tinybird)