🍨

【保存版】DjangoとGhostをVPS1台で同居させる「最適解」なDocker構成

に公開

はじめに

個人開発やスタートアップの初期フェーズにおいて、「自作のWebアプリ(Django)」と「オウンドメディア(Ghost)」を低コストで運用したいというケースはよくあります。

今回は、ConoHa VPS上にDocker環境を構築し、HTTPS-Portalという強力なリバースプロキシコンテナを使って、以下の要件を満たすデプロイ環境を構築した備忘録です。

実現した構成とメリット

構成図

  • VPS: ConoHa VPS
  • Frontend proxy: HTTPS-Portal (Nginx + Let's Encrypt auto-renewal)
  • App A: Django + PostgreSQL (Webアプリ本体)
  • App B: Ghost + MySQL (CMS/ブログ)

この構成のメリット

  1. SSL証明書の自動更新: HTTPS-PortalがLet's Encryptの取得・更新を全自動で行う。
  2. マルチドメイン対応: 環境変数を1行書くだけで、ドメインごとの振り分けが可能。
  3. 静的ファイルの高速配信: DjangoのstaticファイルをNginx(HTTPS-Portal)側から直接配信し、アプリの負荷を下げる。

docker-compose.yml の全体像

以下が今回作成した定義ファイルです。

docker-compose.yml
services:
  # --- Django App Section ---
  db:
    image: postgres:15.2
    container_name: postgres
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: pg_isready -U "${POSTGRES_USER:-postgres}" || exit 1
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    env_file:
      - .env

  app:
    container_name: app
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./static:/app/static
      - ./media:/app/media
    expose:
      - "8000"
    command: sh -c "python manage.py migrate && gunicorn mySite.wsgi:application --bind 0.0.0.0:8000"
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy

  # --- Ghost CMS Section ---
  ghost-db:
    image: mysql:8.0
    container_name: ghost-db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${GHOST_DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ghost
      MYSQL_USER: ghost
      MYSQL_PASSWORD: ${GHOST_DB_PASSWORD}
    volumes:
      - ghost_db_data:/var/lib/mysql

  ghost:
    image: ghost:6.10.2
    container_name: ghost
    restart: always
    depends_on:
      - ghost-db
    ports:
      - "2368:2368"
    environment:
      database__client: mysql
      database__connection__host: ghost-db
      database__connection__user: ghost
      database__connection__password: ${GHOST_DB_PASSWORD}
      database__connection__database: ghost
      url: sampleA_Ghost.com  # 実際の運用ドメインに変更
      NODE_ENV: production
    volumes:
      - ghost_content:/var/lib/ghost/content

  # --- Proxy / SSL Section ---
  https-portal:
    image: steveltn/https-portal:1
    container_name: https-portal
    ports:
      - "80:80"
      - "443:443"
    environment:
      # ここでドメインと転送先コンテナを定義
      DOMAINS: 'sampleA_Ghost.com -> http://ghost:2368, sampleB_Django.com -> http://app:8000'
      STAGE: 'production' # 本番時はproduction, テスト時はstaging
    volumes:
      # Djangoの静的ファイルをNginxから配信するためのマウント
      - ./static:/var/www/vhosts/sampleB_Django.com/static
      - ./media:/var/www/vhosts/sampleB_Django.com/media
      - https-portal-data:/var/lib/https-portal
    depends_on:
      - app
      - ghost

volumes:
  db_data:
  https-portal-data:
  ghost_content:
  ghost_db_data:

設定のポイント解説

この構成を安定稼働させるための重要なポイントを3つ解説します。

1. HTTPS-Portalによるルーティング設定

https-portal コンテナの DOMAINS 環境変数がルーティングの肝です。

DOMAINS: 'your-media-site.com -> http://ghost:2368, your-app-site.com -> http://app:8000'

このようにカンマ区切りで記述するだけで、Nginxの設定ファイルを編集することなく、ドメイン単位のリバースプロキシ設定とSSL証明書の取得が完了します。

2. Djangoの静的ファイル配信(重要)

Djangoを本番環境(Gunicorn)で動かす際、CSSや画像などの静的ファイルはDjango自体では配信しません。通常はNginx等に任せる必要があります。

今回の構成では、Docker Volumeの共有を利用してこれを解決しています。

  1. Djangoコンテナ: ./static に collectstatic でファイルを集める。
  2. HTTPS-Portalコンテナ: 同じ ./static を /var/www/vhosts/ドメイン名/static にマウントする。

HTTPS-Portal(内部のNginx)は、デフォルトで /var/www/vhosts/ドメイン名/ 以下にあるファイルを優先して配信する仕様になっているため、これによりDjangoを経由せずNginxが高速に静的ファイルをレスポンスしてくれます。

静的ファイルの反映

コンテナを起動した直後は、Djangoの静的ファイル(CSSやJavaScript)がボリューム上に存在しないため、管理画面などのレイアウトが崩れた状態になります。

VPS上のターミナルで以下のコマンドを実行し、静的ファイルを一箇所に集めます。

docker-compose exec app python manage.py collectstatic --noinput

3. GhostのURL設定

Ghost側でも url 環境変数を正しく設定する必要があります。これが実際の公開URLと食い違っていると、記事内のリンクや画像パスが壊れる原因になります。

environment:
  url: sampleA_Ghost.com

デプロイ手順

  1. 環境変数の準備 (.env)
    セキュリティのため、パスワード類は .env ファイルに記述します。
.env
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydb
GHOST_DB_ROOT_PASSWORD=rootpass
GHOST_DB_PASSWORD=ghostpass
  1. Djangoの設定調整
    Django側で静的ファイルの出力先や、信頼するプロキシの設定を行います。
settings.py
# settings.py
STATIC_ROOT = BASE_DIR / 'static'
MEDIA_ROOT = BASE_DIR / 'media'

# HTTPS-Portalからのヘッダーを信頼する設定
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
ALLOWED_HOSTS = ['your-app-site.com']
  1. コンテナの起動と初期化
# ビルドと起動
docker-compose up -d --build

# Djangoの静的ファイルを集める
docker-compose exec app python manage.py collectstatic --noinput

これで、数分待てばLet's Encryptの証明書が発行され、両方のサイトがHTTPSで閲覧可能になります。

まとめ

VPS 1台の中に、Python製のWebアプリとNode.js製のCMSを同居させる構成は、初期コストを抑える上で非常に有効です。https-portal を活用することで、複雑になりがちなNginxの設定や証明書管理から解放され、開発そのものに集中できるようになります。

Discussion