집 NAS에 Ghost 블로그를 올리기까지
시놀로지 NAS에서 이미 돌고 있던 도커 서비스들과 같은 틀로 Ghost 6을 올렸다. 배포, 첫 기동, 메일 릴레이, 백업에서 걸린 것들을 그림으로 정리했다.
집에 있는 시놀로지 NAS에서는 이미 도커 서비스 열 개 남짓이 돌고 있다. 자산 관리, 서버 감시 같은 직접 만든 서비스들이다. 블로그도 같은 NAS에, 같은 방식으로 올렸다. 블로그 엔진은 Ghost 6이다.
글보다 그림이 많은 기록이다. 시놀로지에 Ghost를 올리려는 사람이라면 메일과 백업 부분이 특히 쓸모 있을 것이다.
구성: 이미 있는 틀에 끼워 넣기

DB는 NAS에 떠 있는 공용 MySQL 8.4에 ghostDB 하나만 더 만들었다. compose 파일에는 Ghost만 둔다. Dockerfile은 FROM ghost:6-alpine 한 줄이다.
이미지 이름은 ghost가 아니라 ghost-me로 했다. 로컬에서 Docker Hub의 공식 ghost 이미지와 태그가 섞이지 않게 하려는 것이다.
배포: 개발 PC에서 굽고, NAS에서는 갈아 끼우기만

다른 서비스와 같은 파이프라인이다. 개발 PC에서 이미지를 빌드해 tar로 SMB 공유폴더에 놓고, SSH로 NAS에서 컨테이너를 바꾼다.
빌드에 --pull을 붙여 두었다. 그래서 다시 배포하는 것이 곧 Ghost 업데이트다. 이 사실이 뒤의 백업 설계를 정했다.
첫 기동은 10분이 넘었다

첫 기동 때 Ghost는 DB에 표를 100개 넘게 만든다. NAS의 MySQL에서는 10분 넘게 걸렸고, 그동안 사이트는 503을 돌려준다.
컨테이너 교체 단계에서도 한 번 끊겼다. docker-compose의 기본 대기 시간 60초 안에 교체가 끝나지 않아 Read timed out이 났다. COMPOSE_HTTP_TIMEOUT=300으로 늘려 해결했다.

배포가 끝났는지 확인하는 방법도 바꿨다. 공식 이미지에 라벨만 붙인 이미지라, 생성 시각이 방금 빌드한 시각이 아니라 공식 이미지 기준으로 남는다.
content 폴더의 주인

글은 DB에, 이미지와 테마는 content 폴더에 들어간다. Ghost 컨테이너는 node 사용자(uid 1000)로 이 폴더에 쓴다. SMB로 만든 폴더는 주인이 달라서, 방금 받은 Ghost 이미지를 root로 한 번 띄워 주인을 바꾼다.
메일: Relay access denied

Ghost는 관리자 초대, 비밀번호 재설정, 새 기기 로그인 인증에 메일을 쓴다. 같은 NAS의 MailPlus Server로 인증 없이 보내도록 했더니, 외부 주소로 보내는 메일이 거부됐다.
도커 대역 전체를 신뢰 네트워크에 넣으면 간단하다. 하지만 그러면 다른 컨테이너도 인증 없이 외부로 메일을 보낼 수 있다. 그래서 Ghost 스택의 네트워크 대역을 고정하고 그 대역 하나만 등록했다. 대역을 처음 바꿀 때는 네트워크를 새로 만들어야 하므로 down 후 up -d를 했다.

메일이 나가는 것을 확인한 뒤 새 기기 로그인 인증을 켰다.
백업: 배포할 때마다 먼저 뜬다

마이너 업데이트도 DB 마이그레이션을 할 수 있다. 그러면 이미지만 예전 태그로 되돌려서는 DB와 맞지 않는다. 그래서 배포 스크립트가 컨테이너를 바꾸기 직전에 백업을 뜨고, 실패하면 배포를 멈춘다.
덤프는 MySQL 컨테이너 안에서 mysqldump --single-transaction으로 뜬다. 잠금 없이 일관된 스냅샷을 얻는 방법이다.

복원도 시험했다. 표 103개가 모두 맞았다. 첫 백업 파일은 31KB였다. 아직 글이 없으니 당연한 크기다.
정리

새로 배운 것보다 기존 틀에 맞추는 데 든 품이 더 컸다. 걸린 곳은 대부분 "같은 NAS 안이니 괜찮다"는 가정에서 나왔다.

다음 글에서는 이 블로그에 글을 쓰는 흐름을 다룬다. 저장소에 쌓이는 작업 기록에서 글감을 모으고, 초안까지 만드는 과정이다.