結論
GhostPROに課金したほうがはるかに便利でした。
なぜなら、
- メンテナンスが楽
- バックアップが楽
- DBがばぐらない(=つまり不具合が起きにくい)
- でバックアップ用のVPS環境やバックアップのテーマを取るための容量選択のコストも減る
- 月額35ドルのコスパ
ということで、実装はできたにせよ、現実的ではなかったので、VPSはやめましたが。
それでもHTTPS化して実装?はできたのでまずはご報告いたします。
テーマは自作です。カスタムページネーションなど工夫したので後日、共有します。
スペックは下記の通り
VPSのスペック
- メモリ4Gb
- コア4コア
- SSD 100GB
Ghostは文章のみのほうが多いので、このぐらいのスペックでも行けると思いました。
何なら他のサービスで、画像はどうにかなりそうです。
- Amazon S3
- Imgur
- Dropbox API
- Google Cloud Storage
- Firebase Storage
- Microsoft Azure Blob Storage
- ImageKit
ドメイン設定は、CONOHAのマニュアルを見ましょう。
公開鍵もありますが、最初に、作っておいてから、VPS契約するとスムーズです。
公式のドキュメントを参考にしましょう。
さて、実際の手順ですが、そのまま載せます。
自分はこれで動きましたが、動くことは動きますが、メンテナンスの面が大変だと思うのであまり推奨はしません。
Ghostの業者ではありませんが。
VPSで設定する方法
1. VPSの初期セットアップ
まず、ディレクリー構成は最終的にこうなります。
── ghost-blog/
├── ghost
├── mysql/
│ └── my.cnf
├── nginx/
│ └── nginx.conf
├── .env
├── docker-compose.yml
├── monitor.sh
└── backup.sh
1.1 基本的なセキュリティ設定
# システムアップデート
sudo apt update && sudo apt upgrade -y
# 必要なパッケージのインストール
sudo apt install -y ufw fail2ban unattended-upgrades
# 新しいユーザーの作成(rootの直接使用を避ける)
sudo adduser deploy
sudo usermod -aG sudo deploy
# SSH鍵認証の設定
mkdir -p ~/.ssh
# 公開鍵をauthorized_keysに追加
echo "your-public-key" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh
1.2 SSH設定の強化
# SSH設定を編集
sudo nano /etc/ssh/sshd_config
# 以下の設定を追加/変更
Port 2222 # デフォルトポートを変更
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
ClientAliveInterval 600
ClientAliveCountMax 0
# SSH再起動
sudo systemctl restart ssh
1.3 ファイアウォール設定
# UFW基本設定
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 必要なポートのみ開放
sudo ufw allow 2222/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
# ファイアウォール有効化
sudo ufw enable
2. Docker環境のセットアップ
2.1 Dockerのインストール
# 古いDockerの削除
sudo apt remove docker docker-engine docker.io containerd runc
# 公式リポジトリの追加
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Dockerインストール
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# ユーザーをdockerグループに追加
sudo usermod -aG docker $USER
2.2 Docker Composeのインストール
# Docker Compose v2の確認
docker compose version
# 必要に応じて手動インストール
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
3. Ghost環境の構築
3.1 プロジェクトディレクトリの作成
# プロジェクトディレクトリ作成
mkdir -p ~/ghost-blog
cd ~/ghost-blog
# 必要なディレクトリ構造
mkdir -p {nginx,ssl,mysql,ghost/content}
3.2 環境変数ファイルの作成
# .env ファイル作成
cat > .env << 'EOF'
# Ghost設定
GHOST_URL=http://yourdomain.com
GHOST_DB_PASSWORD=your_db_password_here
# MySQL設定
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=ghostdb
MYSQL_USER=ghost
# 環境設定
NODE_ENV=production
TZ=Asia/Tokyo
# 追加の最適化設定
NODE_OPTIONS=--max-old-space-size=512
DB_POOL_MIN=2
DB_POOL_MAX=10
# パフォーマンス最適化
database__pool__min=1
database__pool__max=5
database__connection__acquireConnectionTimeout=60000
database__connection__timeout=60000
database__connection__debug=false
EOF
# 環境変数ファイルのパーミッション設定
chmod 600 .env
3.3 docker-compose.yml
# .env ファイル作成
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
ghost:
image: ghost:5-alpine
container_name: ghost-app
depends_on:
mysql:
condition: service_started
restart: unless-stopped
environment:
url: ${GHOST_URL}
database__client: mysql
database__connection__host: mysql
database__connection__user: ${MYSQL_USER}
database__connection__password: ${GHOST_DB_PASSWORD}
database__connection__database: ${MYSQL_DATABASE}
database__pool__min: 1
database__pool__max: 5
volumes:
- ghost_content:/var/lib/ghost/content
networks:
- ghost-network
# Ghostのリソース制限
deploy:
resources:
limits:
memory: 1G
cpus: '1.5'
reservations:
memory: 256M
cpus: '0.5'
mysql:
image: mysql:8.0
container_name: ghost-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${GHOST_DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
networks:
- ghost-network
# MySQLのリソース制限
deploy:
resources:
limits:
memory: 800M
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.5'
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 60s
timeout: 30s
retries: 5
start_period: 120s
command: >
--default-authentication-plugin=mysql_native_password
--innodb-buffer-pool-size=256M
--max-connections=20
--innodb-log-file-size=64M
--performance-schema=OFF
nginx:
image: nginx:alpine
container_name: ghost-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
- ./nginx/logs:/var/log/nginx
depends_on:
- ghost
networks:
- ghost-network
# Nginxのリソース制限
deploy:
resources:
limits:
memory: 128M
cpus: '0.5'
volumes:
ghost_content:
mysql_data:
networks:
ghost-network:
driver: bridge
EOF
3.4 MySQL設定ファイル
# MySQL設定ファイル作成
cat > mysql/my.cnf << 'EOF'
# mysql/my.cnf - 4GB RAM環境向け最適化設定
[mysqld]
# === 基本設定 ===
bind-address = 0.0.0.0
skip-name-resolve
default-authentication-plugin = mysql_native_password
# === メモリ最適化 (4GB RAM環境向け) ===
# InnoDB バッファプール (RAM の約25-30%)
innodb_buffer_pool_size = 1024M
innodb_buffer_pool_instances = 4
# クエリキャッシュ (MySQL 8.0では廃止されたが設定例として)
# query_cache_type = 1
# query_cache_size = 128M
# テーブルキャッシュ
table_open_cache = 2000
table_definition_cache = 1400
# === 接続設定 ===
max_connections = 50
max_connect_errors = 1000
wait_timeout = 300
interactive_timeout = 300
# === InnoDB 最適化 ===
innodb_log_file_size = 256M
innodb_log_buffer_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
innodb_read_io_threads = 4
innodb_write_io_threads = 4
# === MyISAM 設定 (念のため) ===
key_buffer_size = 32M
myisam_sort_buffer_size = 8M
# === スレッド設定 ===
thread_cache_size = 16
thread_stack = 256K
# === ソート・結合バッファ ===
sort_buffer_size = 2M
join_buffer_size = 2M
read_buffer_size = 1M
read_rnd_buffer_size = 2M
# === 一時テーブル ===
tmp_table_size = 64M
max_heap_table_size = 64M
# === ログ設定 ===
# スロークエリログ
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 0
# エラーログ
log_error = /var/log/mysql/error.log
# === セキュリティ設定 ===
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO
# === その他の最適化 ===
# バイナリログ (レプリケーションしない場合は無効化)
skip-log-bin
# パフォーマンススキーマ (必要に応じて無効化)
# performance_schema = OFF
[mysql]
default-character-set = utf8mb4
[client]
default-character-set = utf8mb4
EOF
3.5 Nginx設定
# Nginx設定ファイル作成
cat > nginx/nginx.conf << 'EOF'
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# ログ設定
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# レート制限(緩和)
limit_req_zone $binary_remote_addr zone=ghostadmin:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=ghost:10m rate=60r/m;
# キャッシュ設定
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=ghost_cache:10m max_size=100m inactive=60m;
proxy_temp_path /tmp/nginx_temp;
# Gzip圧縮
gzip on;
gzip_types text/plain text/css application/json application/javascript text/javascript;
gzip_min_length 1000;
# HTTPS設定
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# 静的ファイルのキャッシュ
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
proxy_pass http://ghost:2368;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 静的ファイルの長期キャッシュ
expires 1M;
add_header Cache-Control "public, immutable";
# プロキシキャッシュ
proxy_cache ghost_cache;
proxy_cache_valid 200 1h;
proxy_cache_use_stale error timeout invalid_header updating;
}
# Ghost管理画面(キャッシュなし)
location /ghost/ {
limit_req zone=ghostadmin burst=15 nodelay;
proxy_pass http://ghost:2368;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
# 管理画面はキャッシュしない
proxy_cache off;
}
# 一般的なページ(軽いキャッシュ)
location / {
limit_req zone=ghost burst=30 nodelay;
proxy_pass http://ghost:2368;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 軽いキャッシュ
proxy_cache ghost_cache;
proxy_cache_valid 200 5m;
proxy_cache_use_stale error timeout invalid_header updating;
# キャッシュバイパス(管理者用)
proxy_cache_bypass $cookie_ghost_admin_api_session;
}
}
# Client
client_max_body_size 100M;
# HTTP to HTTPS redirect
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
}
EOF
4. SSL証明書の設定
4.1 Let's Encryptの使用
# Certbotのインストール
sudo apt install certbot python3-certbot-nginx
# SSL証明書の取得
sudo certbot certonly --webroot -w /var/www/html -d yourdomain.com
# 証明書のコピー
sudo cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem ssl/cert.pem
sudo cp /etc/letsencrypt/live/yourdomain.com/privkey.pem ssl/key.pem
# 証明書の自動更新
sudo crontab -e
# 以下を追加
0 12 * * * /usr/bin/certbot renew --quiet
5. 監視とログ設定
5.1 ログ管理
# ログローテーション設定
sudo nano /etc/logrotate.d/ghost
# 以下を追加
/home/deploy/ghost-blog/nginx/logs/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 root root
postrotate
docker kill -s USR1 ghost-nginx
endscript
}
5.2 監視スクリプト
# 監視スクリプト作成
cat > monitor.sh << 'EOF'
#!/bin/bash
# コンテナの状態チェック
docker ps --format "table {{.Names}}\t{{.Status}}" | grep -E "(ghost|mysql|nginx)"
# ディスク使用量チェック
df -h | grep -E "(/$|/var)"
# メモリ使用量チェック
free -h
# ログの異常チェック
tail -n 50 nginx/logs/error.log | grep -E "(error|warning)"
EOF
chmod +x monitor.sh
6. バックアップ設定
# バックアップスクリプト作成
cat > backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/home/deploy/backups"
DATE=$(date +%Y%m%d_%H%M%S)
# バックアップディレクトリ作成
mkdir -p $BACKUP_DIR
# MySQLバックアップ
docker exec ghost-mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD ghostdb > $BACKUP_DIR/ghost_db_$DATE.sql
# Ghostコンテンツバックアップ
docker run --rm -v ghost_content:/source -v $BACKUP_DIR:/backup alpine tar czf /backup/ghost_content_$DATE.tar.gz -C /source .
# 古いバックアップを削除(7日以上前)
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
EOF
chmod +x backup.sh
# 定期実行設定
crontab -e
# 以下を追加
0 2 * * * /home/deploy/ghost-blog/backup.sh
7. 起動とテスト
# 環境の起動
docker compose up -d
# ログの確認
docker compose logs -f
# 状態確認
docker compose ps
8. セキュリティチェックリスト
- SSH鍵認証の設定完了
- ファイアウォール設定完了
- パスワードを環境変数に設定
- SSL証明書の設定完了
- レート制限の設定完了
- セキュリティヘッダーの設定完了
- ログ監視の設定完了
- バックアップ設定の完了
- 定期更新の設定完了
公開できたなら
ここまでいけば、とお思いでしょう。
でも本格的にやるなら、テーマのバックアップと、Github連携による自動デプロイ設定から、ステージング環境間で用意したほうが良いと思うのでやはり現実的では・・・(ごほごほっ