맥미니+Ubuntu 환경에서 Docker로 Ghost 블로그 설치하기

언제나 즐거은 삽질! 구형 맥미니 2014+Ubuntu 24.04 LTS에 Ghost 블로그를 Docker로 설치하는 방법 (HTTPS + MySQL 분리 네트워크 구성)

지난 포스트에서 맥OS에 도커로 ghost를 설치하는 방법을 소개했는데 그 방법으로 잘되지 않았다. ^^;; 사실 몇날 몇일을 고생했다. 개발자가 아니다 보니 나의 지식이 협소함에 다시금 놀라움을 금치 못했다. 몇 일을 삽질하고 드디어 성공해서 하염없이 기쁘기는 하다.

Ghost 블로그를 HTTPS 보안, Gmail SMTP, DB 분리, 접근 제어까지 적용해서 안정적으로 배포하기 위해 Docker Compose를 사용했습니다. 특히 nginx-proxy + acme-companion 조합을 통해 SSL 인증서를 자동으로 관리하도록 구성했습니다.


디렉토리 구조

/var/www/blog/
├── docker-compose.yml
├── .env
└── data/
    ├── ghost/
    └── mysql/
  • OS : Ubuntu 24.04.2 + Docker 28.3.2

1. 전체 구조

version: "3.9"
services:
  nginx-proxy:
  acme-companion:
  mysql:
  ghost:
networks:
  proxy-tier:
  ghost-internal:
  • nginx-proxy: reverse proxy로서 HTTPS 처리 및 도메인 기반 라우팅을 담당
  • acme-companion: Let's Encrypt 인증서를 자동 발급 및 갱신
  • mysql: Ghost의 DB, 내부 통신 전용
  • ghost: Ghost 블로그 서비스
  • networks: 프록시용 네트워크와 내부 DB 통신용 네트워크를 분리

2. nginx-proxy 설정

  nginx-proxy:
    image: jwilder/nginx-proxy:alpine
    container_name: nginx-proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./certs:/etc/nginx/certs:ro
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html
      - ./data/ghost:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/custom.conf:ro
    environment:
      - TZ=${TZ}
    networks:
      - proxy-tier
  • jwilder/nginx-proxy는 컨테이너 자동 탐지 기반으로 리버스 프록시를 설정해줍니다.
  • nginx.conf는 보안 강화 설정을 위한 커스텀 설정입니다.
  • 인증서 경로와 정적 리소스를 위한 볼륨도 정의되어 있습니다.
  • 도커 소켓을 ro로 마운트하여 보안도 고려했습니다.

3. acme-companion 설정

  acme-companion:
    image: nginxproxy/acme-companion
    container_name: acme-companion
    depends_on:
      - nginx-proxy
    restart: always
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy
      - DEFAULT_EMAIL=${GMAIL_USER}
      - TZ=${TZ}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./certs:/etc/nginx/certs
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html
    networks:
      - proxy-tier
  • NGINX_PROXY_CONTAINER 환경변수는 꼭 필요합니다. 그래야 nginx-proxy 컨테이너와 연동됩니다.
  • 인증서 발급 이메일은 .env 파일에 정의한 Gmail 주소입니다.
  • nginx와 공유되는 디렉토리를 동일하게 마운트해야 인증서 연동이 가능합니다.

4. MySQL 설정 (내부 전용)

  mysql:
    image: mysql:8.0.42
    container_name: ghost-mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: ${TZ}
    volumes:
      - ./data/mysql:/var/lib/mysql
    networks:
      - ghost-internal
  • Ghost에서만 접근 가능한 내부 DB입니다.
  • ghost-internal 네트워크로만 연결되어 외부 노출을 차단했습니다.
  • 비밀번호 및 DB 정보는 .env 파일로 관리합니다.

5. Ghost 블로그 서비스 설정

yaml복사편집 ghost:

  ghost:
    image: ghost:5
    container_name: ${BLOG_CONTAINER_NAME}
    restart: always
    depends_on:
      - mysql
    environment:
      url: https://${DOMAIN}
      database__client: mysql
      database__connection__host: mysql
      database__connection__user: ${MYSQL_USER}
      database__connection__password: ${MYSQL_PASSWORD}
      database__connection__database: ${MYSQL_DATABASE}
      mail__transport: SMTP
      mail__options__service: Gmail
      mail__options__auth__user: ${GMAIL_USER}
      mail__options__auth__pass: ${GMAIL_PASS}
      mail__from: "'mango' <${GMAIL_USER}>"
      TZ: ${TZ}
      VIRTUAL_HOST: ${DOMAIN}
      LETSENCRYPT_HOST: ${DOMAIN}
      LETSENCRYPT_EMAIL: ${GMAIL_USER}
    volumes:
      - ./data/ghost:/var/lib/ghost/content
    networks:
      - proxy-tier
      - ghost-internal
  • url: Ghost가 인식하는 자기 주소. SSL이 적용되었기에 https://로 설정.
  • DB 연결 정보 및 SMTP 설정은 모두 .env에서 관리.
  • VIRTUAL_HOST, LETSENCRYPT_HOST, LETSENCRYPT_EMAIL은 nginx-proxy + acme-companion이 인식하는 키입니다.
  • nginx에서 client_max_body_size 설정도 따로 적용하여 파일 업로드 문제를 예방했습니다.

6. 네트워크 설정

networks:
  proxy-tier:
  ghost-internal:
    internal: true
  • proxy-tier: 외부 요청을 받을 수 있는 네트워크
  • ghost-internal: DB 전용 내부 네트워크 (internal: true로 외부 통신 차단)

✍️ 결론

이 docker-compose.yml은 다음을 만족합니다:

  • 자동 HTTPS (Let's Encrypt)
  • 블로그와 DB 네트워크 분리
  • Gmail SMTP 메일 발송
  • 커스텀 nginx 보안 설정 (nginx.conf)
  • Ghost 관리 페이지 접속 제어 가능 (IP 제한 설정)
  • .env 기반 설정으로 비밀번호 및 도메인 안전하게 관리

docker-compose.yml

전체 구성

version: "3.9"

services:
  nginx-proxy:
    networks:
      - proxy-tier
    image: jwilder/nginx-proxy:alpine
    container_name: nginx-proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./certs:/etc/nginx/certs:ro
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html
      - ./data/ghost:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/custom.conf:ro
    environment:
      - TZ=${TZ}

  acme-companion:
    networks:
      - proxy-tier
    image: nginxproxy/acme-companion
    container_name: acme-companion
    depends_on:
      - nginx-proxy
    restart: always
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy   # 👈 반드시 추가
      - DEFAULT_EMAIL=${GMAIL_USER}
      - TZ=${TZ}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./certs:/etc/nginx/certs
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html

  mysql:
    networks:
      - ghost-internal   # 외부 노출 X
    image: mysql:8.0.42
    container_name: ghost-mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: ${TZ}
    volumes:
      - ./data/mysql:/var/lib/mysql

  ghost:
    networks:
      - proxy-tier       # 외부 접속용
      - ghost-internal   # DB 내부 접속용
    image: ghost:5
    container_name: ${BLOG_CONTAINER_NAME}
    restart: always
    depends_on:
      - mysql
    environment:
      url: https://${DOMAIN}
      database__client: mysql
      database__connection__host: mysql
      database__connection__user: ${MYSQL_USER}
      database__connection__password: ${MYSQL_PASSWORD}
      database__connection__database: ${MYSQL_DATABASE}
      mail__transport: SMTP
      mail__options__service: Gmail
      mail__options__auth__user: ${GMAIL_USER}
      mail__options__auth__pass: ${GMAIL_PASS}
      mail__from: "'mango' <${GMAIL_USER}>"
      TZ: ${TZ}
      VIRTUAL_HOST: ${DOMAIN}
      LETSENCRYPT_HOST: ${DOMAIN}
      LETSENCRYPT_EMAIL: ${GMAIL_USER}
    volumes:
      - ./data/ghost:/var/lib/ghost/content
networks:
  proxy-tier:
  ghost-internal:
    internal: true

추가 보안 설정

✅ 1. 권장 소유자: 현재 사용자 또는 Docker가 접근 가능한 사용자

sudo chown -R $USER:$USER /var/www/blog
  • $USER는 현재 로그인한 사용자
  • Ghost와 MySQL이 파일을 읽고 쓰려면 소유자 권한이 있어야 함

✅ 2. 권장 퍼미션 (디렉토리와 파일)

📁 디렉토리: 755

find /var/www/blog -type d -exec chmod 755 {} \;
  • 의미: 소유자 rwx, 그룹/기타 사용자 rx (읽기+실행)

📄 파일: 644

find /var/www/blog -type f -exec chmod 644 {} \;
  • 의미: 소유자 rw, 그룹/기타 사용자 r (읽기만)

🔒 예외: .env 파일, SMTP 비밀번호 등

.env에는 비밀번호가 들어가므로 보안을 강화하려면:

chmod 600 /var/www/blog/.env
  • 오직 소유자만 읽고 쓸 수 있음

참고 사이트