Ghost Docker로 배포 + S3 연동
Ghost를 Docker로 배포하면서 S3 플러그인을 설치하여 구동하는 방법을 공유합니다.
이 블로그는 Ghost라는 CMS(Content Management System)를 통해서 호스팅되고 있다. Ghost를 처음 배포할 때 고민이 좀 많았는데 아래 이유로 고민이 있었다.
Ghost 구축 전 고민
- 클라우드를 자주 이전하는데 공식 설치 방법인 npm을 통해 설치하는 것은 이전 시에 환경 때문에 문제가 될 수 있다.
- VM에 용량이 많이 없어서 사진 등의 데이터를 저장할 때 문제가 생길 수 있다.
- VM SSD 용량을 확장하는데는 시스템을 손대야하는 등의 문제가 있다.
- 트래픽 문제
Structure
많은 고민을 거친 끝에 아래와 같이 결정했다.
- Ghost는 Docker로 배포하되, Data Directory는 Docker Volume으로서 bind 한다.
- Database도 Docker로 배포한다. Data Directory는 Docker Volume으로서 bind 한다.
- Ghost는 MySQL, SQLite를 지원하지만, SQLite는 곧 지원 중단이다.
- OCI에 Managed MySQL을 배포할까 했지만, 없는 것 같았다. 있어도 유료일거라..!
- S3 Adaper를 이용해서 업로드하는 파일들은 OCI Bucket에 업로드한다.
- 앞 단에 웹 서버를 배치한다.
- SSL 인증서 적용
- 테마의 asset, 업로드 파일 등의 요청은 캐시 정책을 설정한다. 여기를 참고했다.
- 업로드 된 파일 요청 중 오리지널 파일에 접근하는 요청은 차단한다.
- 일부 Endpoint에는 GET Method만 허용한다.
구축하기
Docker Image
먼저, Ghost에서는 Docker를 이용한 설치 방식을 공식적으로 지원하지 않는다. 그러므로 열심히 포럼 등을 찾아서 설치하시면 된다. Ghost Docker Hub를 보면 상세하게 기재되어 있으니 이 내용을 참고하면 된다.
S3 연동은?
Ghost에서는 기본적으로 Local에 저장하는 것을 전재로 한다. 그렇다면 S3을 붙힐 방법은 없는 것인가? 정답은 '있다.'
S3 Integration은 커뮤니티에서 만든 것을 기반으로 사용하면 된다고 한다. 나는 여기서 ghost-storage-adapter-s3을 사용했다.
그런데, Installation을 보면 위와 같이 npm으로 설치하는 것을 전재로 하는데, Docker로 구동하는 경우에는 Container가 재시작될 때마다 파일이 초기화 되기에 문제가 생긴다.
그래서 내가 낸 결론은 별도로 Docker Image를 만드는 것이었다.
# 세팅 당시 최신 버전
FROM ghost:5.99.0-alpine
WORKDIR /var/lib/ghost
# S3 어댑터 설치
RUN npm install ghost-storage-adapter-s3 \
&& mkdir -p content/adapters/storage \
&& cp -r node_modules/ghost-storage-adapter-s3 content/adapters/storage/s3 \
&& rm -rf node_modules
나는 위와 같이 설정했다. 만일, 다른 Adatper를 추가하고 싶다면, RUN 부분에 있는 명령어를 커스텀하면 될 것이다.
docker-compose 구성
다음 할 일은 docker-compose.yaml을 구성하는 것이다. 위에서 만든 Dockerfile을 이용해 구성한다.
# 최근 docker-compose에서는 verison을 기재하지 않는다.
services:
ghost:
build:
context: .
dockerfile: Dockerfile
network: host
restart: always
ports:
- <EXTERNAL PORT>:2368
environment:
# see https://ghost.org/docs/config/#configuration-options
database__client: mysql
database__connection__host: mysql
database__connection__user: <GHOST DB USERNAME>
database__connection__password: <GHOST DB PASSWORD>
database__connection__database: <GHOST DB NAME>
storage__active: s3
storage__s3__accessKeyId: <CUTSOMER SECRET KEY ACCESSKEY>
storage__s3__secretAccessKey: <CUTSOMER SECRET KEY SECRETKEY>
storage__s3__bucket: <BUCKET NAME>
storage__s3__region: <REGION>
storage__s3__endpoint: https://<NAMESPACE>.compat.objectstorage.<REGION>.oraclecloud.com
storage__s3__forcePathStyle: "true"
uploads__maxFileSize: <UPLOAD 최대 사이즈>
url: <GHOST EXTERNAL URL>
TZ: Asia/Seoul
volumes:
- ./ghost/images:/var/lib/ghost/content/images
- ./ghost/themes:/var/lib/ghost/content/themes
- ./ghost/data:/var/lib/ghost/content/data
- ./ghost/logs:/var/lib/ghost/content/logs
# 시간대를 시스템과 맞춤
- /etc/localtime:/etc/localtime:ro
depends_on:
- mysql
mysql:
image: mysql:lts
restart: always
environment:
MYSQL_DATABASE: <GHOST DB NAME>
MYSQL_USER: <GHOST DB USERNAME>
MYSQL_PASSWORD: <GHOST DB PASSWORD>
MYSQL_ROOT_PASSWORD: <DB ROOT PASSWORD>
TZ: Asia/Seoul
volumes:
- ./mysql_data:/var/lib/mysql
- /etc/localtime:/etc/localtime:ro
내가 사용한 docker-compose.yaml 파일이다. 원래 작성해야하는 config.json 파일을 환경변수의 형태로 작성한다고 보면 된다. __로 한 계층을 구분하는 것이다. 만일, 업로드한 데이터를 Ghost 로컬에 저장하고 싶다면 'storage__active' 변수를 없애면 된다.
나처럼 OCI로 구축하시는 분은 이전 글을 참고하면 환경 변수를 구성할 때 어떻게 채워 넣을지 도움을 얻으실 수 있을 것이다.
핵심은 Ghost의 Volume Bind 부분인데, content 디렉터리 전부를 바인딩하는 경우 S3 Adapter가 없어지기 때문에 문제가 생긴다. 그러므로 귀찮지만 Adapter 디렉터리를 제외한 다른 디렉터리들만 바인딩을 해준 것이다.
위 docker-compose.yaml 파일만 그대로 따라해도 바로 배포하여 사용이 가능해질 것이다.