서버 운영 시 중요한 루틴 중 하나로 불리는 정기적인 백업. 그런 밈도 있지 않은가. 디자인과 사람들이 같이 작업하다가 누군가 비명 지르면 모두가 차분하게 ctrl(⌘) + s를 누른다고.. 여하튼 나도 블로그 웹사이트를 만들며 아래와 같은 생각을 했었다. 도커 상태에 따라 웹사이트가 아예 죽기도 했던 경험을 해서 더욱 그러하다.
- 이거 백업 안 하고 운영하다가 언젠가 다 날릴 수도 있지 않을까? 그럼 어떻게 복구하지. 고생해서 만들었는데..
- SSL 인증서 만료되면 곧바로 서비스가 중단될 수 있을 텐데..
누구나 한 번쯤은 해보는 생각이지만 '지금 당장 안 해도 되잖아'라는 이유로 미뤄두기 쉬운 일이다. 그래서인지 강의에도 운영 관리에 대한 파트가 포함되어 있었고, 그에 따라 자동화를 직접 수행해보았다. crontab, 스크립트, 로그 관리까지 정리해보았다.
- Ghost 콘텐츠가 저장된 Docker 볼륨 → 매주 자동 백업
- 백업
.tar.gz
파일 → 날짜별 최신 5개만 남기고 자동 정리 - certbot Docker 컨테이너 → 인증서 주기적 갱신
- 각 작업 → crontab + 로그 파일로 상태 확인 가능
백업 자동화가 필요한 이유
백업은 단순히 데이터를 지키는 것 이상의 의미가 있다
- 책임감: 운영 환경에서는 내가 작성한 코드가 실제 사용자에게 영향을 미친다
- 자동화 사고: 반복적인 작업을 자동화하는 개발자적 사고방식 기르기
- 운영 마인드: 개발뿐만 아니라 서비스 운영에 대한 이해도 높이기
먼저 도커 볼륨 백업 파일로 저장
docker run --rm -v ghost_content:/volume -v $(pwd):/backup alpine sh -c
"cd /volume && tar czf /backup/ghost_content_backup_$(date +%Y%m%d).tar.gz
."
ls
로 잘 백업되었는지 확인해보니 잘 되어 있음. 백업본으로 복원하고 싶다면 아래 코드를 입력.
docker run --rm -v ghost_content:/volume -v $(pwd):/backup alpine sh -c
"cd /volume && tar xzf /backup/ghost_content_backup_YYYYMMDD.tar.gz"
1. Ghost Docker 볼륨 백업 자동화
Ghost는 모든 콘텐츠를 Docker 볼륨(ghost_content
)에 저장한다. 따라서 이 볼륨을 crontab을 활용해 주기적으로 .tar.gz
파일로 백업해두는 게 좋다.
우선 리눅스에서 크론탭 편집 모드로 들어간다.
crontab -e
처음 실행 시 사용할 편집기를 선택하라는 메시지가 뜨는데 초보자들도 쓰기 쉽다는 nano
를 선택했다. vi 에디터도 써보고 싶은데 이건 나중에 좀 더 공부해보고 시도해봐야 할 것 같다.
1.의 nano 선택 후크론탭 편집 모드에 진입하면 아래처럼 기본 설명이 표시된다.

crontab에 백업 명령 작성
0 3 * * 0 /usr/bin/docker run --rm -v ghost_content:/volume -v /home/ubuntu:/backup alpine sh -c "cd /volume && tar czf /backup/ghost_content_backup_$(date +\%Y\%m\%d).tar.gz ."
- 매주 일요일 오전 3시 실행
ghost_content
: Docker 볼륨 이름/home/ubuntu
: 백업 파일이 저장될 디렉토리tar.gz
: 날짜 기반 압축 파일로 저장/home/ubuntu/ghost_content_backup_YYYYMMDD.tar.gz
형식으로 저장%
는 crontab에서 특수문자라서\%
로 이스케이프하지 않으면 저장이 안 된다.
나는 처음 저장할 때 crontab이 깨지는 경험을 했다.
nano 에디터에서 크론탭 저장하는 법
- 저장:Ctrl + O
→Enter
- 종료:Ctrl + X
크론 표현식 참고
크론탭 문법이 처음이라면 이 표를 참고해보자.
# m h dom mon dow command
필드 | 의미 |
---|---|
m |
분 (0–59) |
h |
시 (0–23) |
dom |
일 (1–31) |
mon |
월 (1–12) |
dow |
요일 (0–7, 일요일은 0 또는 7) |
command |
실행할 명령어 |
예를 들어 매일 새벽 3시에 명령어를 실행하고 싶다면 다음처럼 쓴다. 온라인 cron 표현식 생성기를 활용하면 쉽게 작성 가능하다.
0 3 * * * /home/ubuntu/backup.sh
수동 실행 테스트와 결과 메시지
기존에 입력한 크론 명령어는 매주 일요일 새벽 3시에 백업을 수행하도록 하는 것이었다. 그때까지 기다릴 수 없으니 정상 작동 여부를 수동 명령어를 통해 확인해야 한다.
docker run --rm -v ghost_content:/volume -v /home/ubuntu:/backup alpine sh -c "cd /volume && tar czf /backup/ghost_content_backup_$(date +%Y%m%d).tar.gz ."
그럼 ls
입력 시 ghost_content_backup_20250716.tar.gz
파일이 생성되어 있는 게 보인다.
2. 백업 파일 정리 자동화 (최신 5개만 유지)
백업 파일 .tar.gz
가 백업 파일이 무한히 쌓이면 디스크 용량 문제가 생길 수 있다. 용량도 용량이지만 쌓이기만 하고 관리되지 않는 자동화는 결국 또다른 부채가 된다. 최신 5개만 유지하고 나머지를 자동 삭제하는 셸 스크립트를 작성했다.
스크립트 : cleanup_old_backups.sh
#!/bin/sh
# 백업 디렉토리 경로
BACKUP_DIR="/home/ubuntu"
# 백업 파일 목록에서 최신 5개를 제외한 나머지를 삭제
cd "$BACKUP_DIR"
ls -1t ghost_content_backup_*.tar.gz | tail -n +6 | while read file; do
rm -f "$file"
done
- 가장 최근 5개를 제외한
.tar.gz
파일을 삭제 xargs
대신while read
를 쓴 이유: 파일명에 공백이 있어도 안전
처음에는 이렇게 썼지만 ls -lt ghost_content_backup_*.tar.gz | tail -n +6 | xargs rm -f
결과는 rm: invalid option – 'w'
였다. ls
결과의 첫 컬럼이 파일명이 아니라 권한(-rw-r--r--) 이다 보니 xargs
가 제대로 동작하지 않았다.
ls -lt
는 파일 목록을 다음과 같이rm -f -rw-r--r– 1 ubuntu ubuntu 20480 Jul 20 10:31 ghost_content_backup_20240720.tar.gz
로 전체를 출력한다. 이때xargs rm -f
가 같이 쓰이면 전체 라인을 하나의 인자로 받아서rm
은-rw-r--r--
를 옵션으로 잘못 인식하고 오류를 낸다. 긴 줄 전체가xargs
로 넘어가면서rm
이 엉뚱한 걸 인자로 받게 된 상황이다.ls -lt
에서-l
과-t
의 결합은 아래와 같다.-l
→ 긴 형식 출력 (파일 메타 정보까지 포함)-t
→ 파일을 수정 시간 기준으로 내림차순 정렬
이는-l
를-1
대체하고while read
조합으로 하면 해결된다.-1
(숫자 1) → 한 줄에 파일 하나씩 출력하라는 옵션while read file
은 각 줄을 하나씩 읽고 공백이 있는 파일명도 안전하게 처리
정상 작동:ls -1t
+while read
"파일 목록을 최근 수정 시간 순으로 정렬, 각 파일 이름을 한 줄에 하나씩 출력"
xargs
에 대해서도 더 알아봤다.
표준 입력(stdin)으로 받은 값을 명령어의 인자(argument)로 넘겨주는 도구
어떤 명령어의 인자(파일 이름, 문자열 등)가 파일에서 나오거나 파이프로 전달되는 경우,xargs
는 그 값을 받아서 명령어 뒤에 자동으로 붙여주는 역할.xargs
는 특히 파이프(|)와 함께 쓰일 때 진가를 발휘한다.
예시)cat
list.txt | xargsecho
에 [apple, banana, carrot]가 담겨 있다면
list.txt
내부적으로 아래처럼 변환돼 실행echo
apple banana carrot
crontab에 추가 입력
앞서 1. 단계에서는 백업을 주기적으로 하는 크론 명령을 넣었다. 이 단계에서는 저장된 백업 파일들을 자동으로 정리하는 명령을 추가로 작성해서 넣는다. 위에서 짜놓은 스크립트가 정해진 시간에 실행되도록 하는 것이다.
0 0 1 * * /home/ubuntu/cleanup_old_backups.sh > /home/ubuntu/cleanup.log 2>&1
- 매월 1일 자정 실행
- 최근 실행 로그 덮어쓰기
crontab: installing new crontab
입력해 둔 크론탭을 확인하고 싶을 때에는 crontab -l
을 입력하면 된다.
3. SSL 인증서 갱신 자동화 (Let's Encrypt + certbot)
이제 마지막 단계다. 인증서는 Let's Encrypt를 쓰고 있었고 기존에 certbot을 Docker 컨테이너로 실행하고 있었다. 인증서 자동 갱신 스크립트를 짜서 주 1회 자동 실행, 인증서는 만료 30일 이내일 때만 실제 갱신이 진행된다.
스크립트: renew_cert.sh
#!/bin/sh
docker run --rm \
-v "/home/ubuntu/103_ONEDEV_STEP3/certbot-etc:/etc/letsencrypt" \
-v "/home/ubuntu/103_ONEDEV_STEP3/nginx1:/usr/share/nginx/html" \
certbot/certbot renew --webroot --webroot-path=/usr/share/nginx/html
- 실제 갱신은 인증서 만료 30일 전부터만 시도됨
crontab 등록
0 0 * * 0 /home/ubuntu/103_ONEDEV_STEP3/renew_cert.sh >> /home/ubuntu/103_ONEDEV_STEP3/renew_cert.log 2>&1
- 매주 일요일 자정 실행
- 로그는
renew_cert.log
에 누적 저장되도록 처리/home/ubuntu/103_ONEDEV_STEP3/renew_cert.log 2>&1
- 처음에 위 부분을 빼먹어서 로그 파일이 생성되지 않았다. 직접 실행할 때에도
>> logfile 2>&1
을 붙이면 로그 파일 저장 가능
renew_cert.sh
스크립트를 실행해보면 아래와 같은 로그가 나온다. 나의 인증서 만료 기한은 10-13일이므로 만료 30일 이내가 되지 않았기에 갱신을 시도하지 않는다는 메시지이다.
The following certificates are not due for renewal yet:
/etc/letsencrypt/live/give-it-a-shot.site/fullchain.pem expires on 2025-10-13 (skipped)
No renewals were attempted.
실행하려는데 Permission denied가 뜬다면 실행 권한을 먼저 부여chmod +x /home/ubuntu/103_ONEDEV_STEP3/renew_cert.sh
4. 최종 crontab 요약

- ghost docker 볼륨 백업 자동화
# 1. 매주 일요일 03:00 백업 생성
0 3 * * 0 /usr/bin/docker run --rm -v ghost_content:/volume -v /home/ubuntu:/backup alpine sh -c "cd /volume && tar czf /backup/ghost_content_backup_$(date +\%Y\%m\%d).tar.gz ."
- 백업 파일 정리 자동화
# 2. 매월 1일 00:00 백업 정리 + 최근 실행 로그 덮어쓰기
0 0 1 * * /home/ubuntu/cleanup_old_backups.sh > /home/ubuntu/cleanup.log 2>&1
- SSL 인증서 갱신 자동화
# 3. 매주 일요일 자정 00:00에 renew_cert.sh 스크립트 실행하고 실행 결과를 renew_cert.log파일에 기록
0 0 * * 0 /home/ubuntu/103_ONEDEV_STEP3/renew_cert.sh >>/home/ubuntu/103_ONEDEV_STEP3/renew_cert.log 2>&1
이렇게 3가지를 구성해두면 운영 중인 Ghost 블로그는 다음과 같은 장점을 가진다.
- 백업을 수동으로 할 필요도, 백업 시기를 놓치는 일도 없다.
- 백업 파일이 알아서 관리 되니 무한정 쌓이지 않는다.
- SSL 인증서 만료로 인한 장애를 걱정하지 않아도 된다.
Docker 기반의 워크플로우에 맞춰 자동화된 백업 + 인증서 시스템을 구성하고 싶다면 이 구성이 좋은 출발점이 될 수 있다.
마무리하며
자동화는 실행보다 정리까지 설계해야 끝난다는 걸 이번에 실감했다.
백업만 만들고 정리를 안 하면 결국 또 다른 일거리로 되돌아올 것이다.
cron은 예외처리 없는 자동화다. 작은 문법 하나, 예를 들어 %
이스케이프를 깜빡하는 것만으로 전체 스케줄이 깨진다. 한 줄 명령이 '그대로 실행될 거라 믿고 저장'하는 구조이기 때문에 더더욱 조심스럽게 다뤄야 하고 그렇기에 수동 테스트도 해봤다.
특히 xargs
와 ls
조합에서 오류를 만났을 때 출력 형식에 따라 명령어 조합이 얼마나 쉽게 어긋날 수 있는지 알게 됐다. 출력 형식 하나 바뀐 것만으로 rm
이 엉뚱한 인자를 받고 실패하는 경험을 하며 -l
, -1
, while read
같은 옵션의 의미를 단순히 외우는 게 아니라 맥락 속에서 체득할 수 있었다. 또한 -l
, -1
처럼 비슷하게 생긴 문자를 입력할 때 주의가 필요할 것 같다. 왜 오류가 났는지 디버깅하는 과정에서 xargs
, while read
같은 옵션들의 의미가 또렷하게 와닿았다.
로그 처리에서도 마찬가지다. >
는 덮어쓰기, >>
는 누적 저장. 겉보기엔 비슷하지만 결과는 완전히 다르다. 자동화의 작은 디테일 하나가 운영 효율과 안정성에 큰 차이를 만든다는 걸 체감했다.
이전에 GCP 스케줄러 처리를 앞두고 "정해진 시점에 실행되게 하는 자동화 프로세스" 정도로만 생각했었는데. 이번 작업을 통해 "어느 시점의 백업을 남겨야 하는지", "인증서 갱신은 얼마나 자주 할지" 등 운영자의 시선에서 설계해야 할 요소가 훨씬 많다는 걸 알게 됐다.