git 도입 + “검증 후 삭제” 원칙으로 529M → 133M

SELF-HOSTING 운영기 · 2/3

🧹 git 도입 + “검증 후 삭제” 원칙으로 529M → 133M

진단으로 진범(중복 백업 397M)과 근본 원인(git 부재)을 찾았다. 이제 정리할 차례. 핵심은 단순 삭제가 아니라 — 안전망을 먼저 확인하고 나서만 지운다는 순서의 설계다.

#git #경량화 #tar #gitignore #운영 #롤백

📋 이 글에서 다루는 것

  • 1. 왜 git이 먼저인가 — 삭제의 전제 조건
  • 2. 1단계: 소스 영구 보존 (패치 이력 포함)
  • 3. 2단계: 런타임 무관 파일 삭제
  • 4. 3단계: 백업 단일화 — “검증 후 삭제” 원칙
  • 5. 결과 — 75% 경량화와 clean 트리
1편의 결론은 명확했다. 이 디렉토리의 부채는 “파일이 많은 것”이 아니라 “파일 복사로 버전 관리를 흉내내는 것”이었다. 그래서 정리의 첫 단계는 삭제가 아니라 git 도입이다. git이 생기면 패치 스크립트와 .bak 파일은 “지워도 영원히 복구 가능”해지고, 그제서야 진짜 안전하게 삭제할 수 있다.

1. 왜 git이 먼저인가

검증으로 “패치를 지워도 기능은 유지된다”는 건 확인했다. 하지만 “나중에 어떤 패치가 무슨 코드를 바꿨는지 추적할 수단”은 여전히 필요하다. 파일을 그냥 지우면 그 이력이 사라진다.

git을 먼저 도입하고 패치 스크립트까지 첫 커밋에 포함시키면 — 작업 디렉토리에서 파일을 지워도 git log에 영원히 남는다. 이게 “파일 복사로 버전관리 흉내내기”를 끝내는 지점이다. 삭제의 전제 조건이 git인 이유다.

⚠ git 도입 전 확인: 정말 git이 없었다

ls -la .git로 확인한 결과 버전 관리가 전무했다. 즉 지금까지 유일한 롤백 수단이 .bak 파일과 백업 디렉토리였던 것. 이걸 그냥 지웠다면 진짜로 복구 불가능한 상태가 됐을 것이다. git 없는 환경에서 백업 파일은 함부로 못 지운다 — 그게 안전망의 전부니까.

2. 1단계 — 소스 영구 보존

1git 초기화 + .gitignore 설계
data/, backups/는 ignore. 소스 + 패치 스크립트는 추적. 데이터는 git이 아니라 별도 백업 전략으로 관리한다.
cd /opt/my-flashcard
git init
printf 'data/\n*.bak-*\nbackups/\ndata-backup-bc-*/\n__pycache__/\n*.pyc\n' > .gitignore

git add app.py templates/*.html Dockerfile docker-compose.yml \
        requirements.txt patch_*.py migrate_base64.py
git commit -m "Phase 16 baseline: 전체 소스 + 패치 이력 보존"
💡 신원 미설정 에러는 정상

처음 커밋 시 Please tell me who you are 에러가 날 수 있다. 이건 실패가 아니라 git이 작성자 정보를 요구하는 것. 로컬 전용 저장소라 실제 이메일일 필요도 없다. --global 없이 이 저장소에만 설정해 서버 전역 오염을 막는다.

git config user.email "me@flashcard.local"
git config user.name "flashcard-admin"

핵심은 패치 스크립트(patch_*.py)도 첫 커밋에 포함시켰다는 점이다. 작업 디렉토리에서 지워도 git show <commit>:patch_tags_v1.py로 언제든 꺼내 볼 수 있다. 이력이 영구 보존된다.

3. 2단계 — 런타임 무관 파일 삭제

git이 이력을 대체하므로, 이제 작업 디렉토리에서 패치/백업 파일을 지워도 진짜 안전하다.

2패치 스크립트 + 소스 백업 제거
rm -f patch_*.py migrate_base64.py
rm -f app.py.bak-* requirements.txt.bak-* templates/*.bak-*

git은 이 삭제를 “변경사항”으로 추적한다. 이것도 커밋해서 기록을 남긴다 — 첫 커밋에 패치가 보존돼 있으므로, 이 커밋은 “작업본에서 제거”를 기록하는 것이다.

삭제도 커밋 (이력 완성)
git add -A
git commit -m "패치 스크립트 제거: git 이력으로 대체, 작업 디렉토리 경량화"
📌 이 시점의 커밋 구조

baseline(전체 보존) → gitignore → 청소 3개 커밋이 됐다. 패치 내용은 첫 커밋에 영원히 남아 있고, 작업 디렉토리는 가벼워졌다. “이력은 보존하되 작업 공간은 깨끗하게” — git이 존재하는 이유 그 자체다.

4. 3단계 — 백업 단일화 (검증 후 삭제 원칙)

이제 진짜 용량인 백업 디렉토리 3개(397M) 차례다. 여기서 이 작업 전체를 관통하는 원칙이 적용된다: 안전망이 멀쩡한 걸 눈으로 확인하기 전에는 원본을 절대 지우지 않는다.

3라이브 데이터 단일 스냅샷 생성
백업 3개를 지우기 전에, 현재 라이브 data/를 압축 스냅샷 하나로 만든다. 이게 새 안전망이 된다.
mkdir -p /opt/_archive
tar czf /opt/_archive/flashcard-data-$(date +%Y%m%d).tar.gz data/
4스냅샷 무결성 검증 (★ 삭제의 전제)
tar가 정상인지 — exit 0과 파일 목록을 눈으로 확인. 이게 OK여야만 다음으로 넘어간다.
tar tzf /opt/_archive/flashcard-data-*.tar.gz | head -5
echo "tar exit code: $?"    # 0이면 정상
⚠ 순서가 안전의 전부다

tar 생성 → 무결성 검증 → 그제서야 원본 백업 삭제. 이 순서를 뒤집으면 “백업을 만들었다고 믿었는데 사실 깨져 있었던” 최악의 시나리오가 가능하다. 검증 단계를 건너뛰는 백업은 백업이 아니라 희망사항이다.

5검증 OK 확인 후에만 — 중복 백업 3개 삭제
du -sh backups/ data-backup-bc-*/    # 지우기 직전 마지막 눈 확인
rm -rf backups/
rm -rf data-backup-bc-20260514-074031/
rm -rf data-backup-bc-20260520-005801/
6최종 검증 — 앱 정상 + 용량 + git 상태
컨테이너만 재시작(빌드 아님). 삭제가 앱에 영향 없는지 로그로 실증.
docker compose restart
sleep 5 && docker compose logs --tail 15 flashcard-app   # 에러 없으면 OK
du -sh /opt/my-flashcard/                                # 용량 확인
git status                                               # clean 확인

5. 결과 — 75% 경량화

529M → 133M 75% 감소 · 기능 100% 유지
항목BeforeAfter
디렉토리 용량529M133M
버전 관리없음 (파일 복사)git 3커밋, 패치 이력 보존
남은 파일패치14 + 백업8 + 소스실사용 6 + .git + .gitignore
기능—마커 검증 완료 · 정상 기동

삭제 후 ls -la에 남은 것은 app.py, templates/, data/, Dockerfile, docker-compose.yml, requirements.txt, 그리고 .git/ + .gitignore — 딱 실사용 파일과 버전 관리뿐이다.

🎯 이번 단계의 교훈

경량화의 75%는 patch 삭제가 아니라 백업 단일화에서 나왔다(390M). patch 14개 삭제는 0.5MB짜리 시각적 정리였을 뿐이다. 데이터로 이 둘을 구분해낸 게 핵심 — “지저분함”과 “무거움”은 다른 문제이고, 다른 해법이 필요했다.

그리고 git 도입으로 “백업 디렉토리 또 쌓임 → 또 청소”라는 사이클의 절반을 끊었다. 나머지 절반(데이터 백업의 자동화)은 다음 편의 주제다.

⚠ 아직 끝나지 않은 리스크

133M 중 132M가 라이브 data/이고, 그 사본은 /opt/_archive의 tar 하나뿐이다. 같은 서버, 같은 디스크. 디스크가 죽으면 라이브와 백업이 동시에 증발한다. 이건 백업이 아니라 “백업이 있다는 착각”이다. 3편에서 이 단일 장애점(SPOF)을 제거한다.