【徹底解説】DifyをAWS負荷分散環境で構築
【Terraformソース有】

2026-03-06 | AWS・クラウド | Shinichi Noguchi

【技術相談】本件の内容に関して30分間の無料相談承ります →

この記事をシェア

はじめに — なぜDifyをAWSに載せるのか

Difyは、LLMアプリケーションを素早く構築できるオープンソースプラットフォームです。RAGパイプライン、ワークフロー、エージェント機能を備え、docker compose upで簡単に起動できるのが魅力ですが、本番環境で運用するとなると話は別です。

公式のdocker-compose.yamlは、PostgreSQL・Redis・Weaviate・RabbitMQ・nginxなど10個以上のコンテナを1台のサーバーで動かす前提です。これでは:

  • DBが壊れたらすべてのデータが消える
  • サーバー1台に障害が起きるとサービス全停止
  • トラフィック増加時にスケールできない
  • バックアップ・パッチ適用を手動で管理する必要がある

本記事では、Dify 1.13.0をAWS App Runner上にデプロイし、ステートフルな部分をすべてAWSマネージドサービスに外出しすることで、スケーラブルで運用コストの低い構成を実現した方法を、Terraformコード付きで徹底解説します。

💡 この記事で得られること

  • App Runner / ECS / EKS の使い分け判断基準
  • 「ステートレス vs ステートフル」で考えるクラウド設計の鉄則
  • Dify 1.13.0のAll-in-Oneコンテナ構築の全ソースコード
  • 実際にハマった10個のポイントと解決策
  • Terraform モジュール構成の全体像

なぜApp Runnerを選んだのか

AWSでコンテナを動かす3つの選択肢

項目App RunnerECS FargateEKS
設定の複雑さ⭐ 最も簡単⭐⭐ やや複雑⭐⭐⭐ 最も複雑
ALB(ロードバランサ)不要(組み込み)別途必要別途必要
TLS/HTTPS自動ACM + ALBで設定Ingress設定
オートスケール組み込み設定が必要HPA設定が必要
VPC統合VPC Connectorネイティブネイティブ
最小コスト(開発環境)~$30/月~$50/月(ALB込み)~$150/月(クラスタ+ALB)
Terraformリソース数5-815-2030+

App Runnerを選んだ3つの理由

1. ALBが不要 — コストと複雑さを削減

ECS Fargateを使う場合、HTTPSでのアクセスにはALB(Application Load Balancer)が必須で、それだけで月額約$20。App RunnerはHTTPSとロードバランシングが組み込みなので丸ごと不要です。

2. デプロイが圧倒的にシンプル

App Runnerは「ECRにイメージをpush → デプロイ開始」するだけ。ECSだとタスク定義の更新 → サービスの更新 → デプロイ待ちと手順が多い。Terraformリソース数もECSの半分以下です。

3. 開発環境には十分すぎる

今回の用途は開発・検証環境です。本番で大量のリクエストを捌く必要が出てきたらECSへの移行を検討しますが、開発段階では「動くまでの最短距離」を優先。App Runnerからの卒業は、Terraformでインフラコード管理しているので容易です。

⚠️ App Runnerの制約

  • コンテナは1つだけ — サイドカーパターンは使えない
  • WebSocketは非対応 — リアルタイム通信が必要ならECSを検討
  • 永続ストレージなし — ファイルシステムはエフェメラル

DifyのSSE(Server-Sent Events)による応答ストリーミングはHTTPベースなので問題なく動作します。

設計の鉄則 — ステートレスとステートフルを分離する

「ステートレスなものはコンテナに、
ステートフルなものはマネージドサービスに」

なぜ分離が必要なのか

公式docker-composeではデータがDockerボリュームとして1台のサーバーに保存されます。オートスケールでコンテナが2台に増えた場合、新しいコンテナには前のデータがありません。コンテナ内にデータを持っている限り、スケールできないのです。

Difyの構成要素を分類する

コンポーネント種別配置先
API Server(Flask/Gunicorn)ステートレスコンテナ内
Celery Workerステートレスコンテナ内
Web UI(Next.js)ステートレスコンテナ内
Plugin Daemonステートレスコンテナ内
nginx(リバースプロキシ)ステートレスコンテナ内
PostgreSQLステートフル→ RDS
Redisステートフル→ ElastiCache
ファイルストレージステートフル→ S3
ベクトルDB(pgvector)ステートフル→ RDS(同居)

なぜマネージドサービスなのか

「EC2にPostgreSQLを自分でインストールすれば安い」という声もありますが、以下を自分でやる覚悟が必要です:

  • 自動バックアップ — RDSなら設定一行。セルフならpg_dump + cronをメンテナンス
  • セキュリティパッチ — RDSは自動適用。セルフなら手動
  • フェイルオーバー — RDS Multi-AZなら自動切替。セルフならレプリケーション構築
  • モニタリング — RDSはCloudWatch統合。セルフならPrometheus等を自前構築

開発環境だからこそ運用にかける時間を最小化したい。マネージドサービスの月額差額は、エンジニアの時間単価と比較すれば誤差です。

全体構成図

┌─────────────────────────────────────────────────────────────────┐
│                    AWS Cloud (ap-northeast-1)                    │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                   VPC (10.0.0.0/16)                         │ │
│  │                                                             │ │
│  │  Public Subnets ─── [ NAT Gateway ]                         │ │
│  │                                                             │ │
│  │  Private Subnets                                            │ │
│  │   ┌────────────────────────────────────────────────────┐   │ │
│  │   │              App Runner (VPC Connector)             │   │ │
│  │   │                                                     │   │ │
│  │   │   ┌──────────────────────────────────────────┐     │   │ │
│  │   │   │      All-in-One コンテナ (:5001)          │     │   │ │
│  │   │   │                                           │     │   │ │
│  │   │   │  nginx (:5001) ─┬─ /api/     → API:5100  │     │   │ │
│  │   │   │  (supervisord)  ├─ /console/ → API:5100  │     │   │ │
│  │   │   │                 └─ /*        → Web:3000  │     │   │ │
│  │   │   │                                           │     │   │ │
│  │   │   │  [API:5100] [Worker] [Web:3000] [Plugin]  │     │   │ │
│  │   │   │  gunicorn   celery   Next.js    Daemon    │     │   │ │
│  │   │   └──────────────────────────────────────────┘     │   │ │
│  │   └────────────────────────────────────────────────────┘   │ │
│  │                    │           │           │                │ │
│  │                    ▼           ▼           ▼                │ │
│  │   ┌─────────┐ ┌─────────┐ ┌─────────┐                    │ │
│  │   │  RDS    │ │ElastiCache│ │   S3    │                    │ │
│  │   │ PG16   │ │ Redis 7.1│ │(storage)│                    │ │
│  │   │+pgvector│ │t4g.micro │ │         │                    │ │
│  │   └─────────┘ └─────────┘ └─────────┘                    │ │
│  └────────────────────────────────────────────────────────────┘ │
│  [ ECR ] ← Dockerイメージ保存                                   │
└─────────────────────────────────────────────────────────────────┘

月額コスト概算

サービススペック月額概算
App Runner1vCPU / 2GB RAM~$30
RDS PostgreSQLdb.t4g.micro, 20GB gp3~$15
ElastiCache Rediscache.t4g.micro~$12
NAT Gateway1 AZ~$35
S3 + ECR従量(少量)~$2
合計~$94/月

💡 コスト削減のヒント

最大のコスト要因はNAT Gateway(~$35/月)です。開発環境ではVPC Endpointやパブリックサブネット配置で回避する手もありますが、セキュリティとのトレードオフです。

Terraform モジュール構成

terraform/
├── modules/
│   ├── vpc/          # VPC, Subnet, NAT GW, Security Groups
│   ├── rds/          # PostgreSQL 16 + pgvector
│   ├── elasticache/  # Redis 7.1
│   ├── s3/           # ファイルストレージ
│   ├── ecr/          # コンテナレジストリ
│   ├── apprunner/    # App Runner + IAM + VPC Connector
│   └── ssm/          # パラメータストア
└── development/
    ├── main.tf
    ├── variables.tf
    ├── terraform.tfvars
    ├── providers.tf
    └── outputs.tf

App Runner モジュール(核心部分)

resource "aws_apprunner_service" "main" {
  service_name = "${var.project}-${var.environment}"

  source_configuration {
    image_repository {
      image_identifier      = "${var.ecr_repository_url}:latest"
      image_repository_type = "ECR"

      image_configuration {
        port = "5001"   # nginx がリッスンするポート

        runtime_environment_variables = {
          # Core
          SECRET_KEY = random_password.secret_key.result

          # Database (RDS)
          DB_HOST     = var.db_host
          DB_USERNAME = var.db_username
          DB_PASSWORD = var.db_password
          DB_DATABASE = var.db_database

          # Redis (ElastiCache)
          REDIS_HOST    = var.redis_host
          REDIS_USE_SSL = "false"

          # Celery Broker → Redis (NOT RabbitMQ!)
          CELERY_BROKER_URL = "redis://${var.redis_host}:6379/1"
          BROKER_USE_SSL    = "false"

          # S3
          STORAGE_TYPE   = "s3"
          S3_BUCKET_NAME = var.s3_bucket_name
          S3_REGION      = var.aws_region

          # Vector DB (pgvector on same RDS)
          VECTOR_STORE      = "pgvector"
          PGVECTOR_HOST     = var.db_host
          PGVECTOR_USER     = var.db_username
          PGVECTOR_PASSWORD = var.db_password

          # Plugin Daemon
          PLUGIN_API_URL            = "http://127.0.0.1:5002"
          PLUGIN_API_KEY            = random_password.secret_key.result
          PLUGIN_DAEMON_KEY         = random_password.secret_key.result
          INNER_API_KEY_FOR_PLUGIN  = random_password.secret_key.result
          MARKETPLACE_ENABLED       = "true"
        }
      }
    }
    auto_deployments_enabled = false
  }

  network_configuration {
    egress_configuration {
      egress_type       = "VPC"
      vpc_connector_arn = aws_apprunner_vpc_connector.main.arn
    }
  }

  health_check_configuration {
    protocol = "HTTP"
    path     = "/health"
    interval = 10
  }
}

All-in-One コンテナの設計

App Runnerは1サービスにつき1コンテナ。Difyは本来5つのプロセスが必要なので、supervisordで統合しました。

Dockerfile — マルチステージビルド

FROM langgenius/dify-api:latest AS api
FROM langgenius/dify-web:latest AS web
FROM langgenius/dify-plugin-daemon:0.5.3-local AS plugin-daemon

FROM langgenius/dify-api:latest
USER root

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    nginx supervisor postgresql-client && \
    rm -rf /var/lib/apt/lists/*

# Web frontend
COPY --from=web /app /app-web

# Plugin daemon binary
COPY --from=plugin-daemon /app/main /app/plugin-daemon/main
COPY --from=plugin-daemon /app/.tiktoken /app/plugin-daemon/.tiktoken
RUN chmod +x /app/plugin-daemon/main && \
    mkdir -p /app/plugin-daemon/storage/cwd

COPY nginx.conf /etc/nginx/sites-available/default
COPY supervisord.conf /etc/supervisor/conf.d/dify.conf
COPY init-db.sh start.sh run-plugin-daemon.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/*.sh

ENTRYPOINT []
EXPOSE 5001
CMD ["/usr/local/bin/start.sh"]

💡 ポイント: ENTRYPOINT [] の上書き

dify-apiのベースイメージには/entrypoint.shがENTRYPOINTとして設定されています。CMDだけ変更してもENTRYPOINTが優先されるため、ENTRYPOINT []で明示的に空にする必要があります。

supervisord.conf — 5プロセス管理

[supervisord]
nodaemon=true
user=root

[program:api]
command=/bin/bash /entrypoint.sh
directory=/app/api
environment=MODE="api",DIFY_PORT="5100",MIGRATION_ENABLED="true"
autorestart=true

[program:worker]
command=/bin/bash /entrypoint.sh
directory=/app/api
environment=MODE="worker"
autorestart=true
startretries=10

[program:web]
command=node /app-web/web/server.js
directory=/app-web/web
environment=PORT="3000",HOSTNAME="0.0.0.0",
  NEXT_PUBLIC_API_PREFIX="/console/api",
  NEXT_PUBLIC_PUBLIC_API_PREFIX="/api",EDITION="SELF_HOSTED"

[program:plugin-daemon]
command=/usr/local/bin/run-plugin-daemon.sh
directory=/app/plugin-daemon
autorestart=true
startretries=30

[program:nginx]
command=nginx -g "daemon off;"
autorestart=true

nginx.conf — リバースプロキシ

server {
    listen 5001;
    client_max_body_size 15M;

    # API routes → Flask/Gunicorn (:5100)
    location /console/api/ {
        proxy_pass http://127.0.0.1:5100/console/api/;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 300s;
    }

    location /api/  { proxy_pass http://127.0.0.1:5100/api/; ... }
    location /v1/   { proxy_pass http://127.0.0.1:5100/v1/;  ... }
    location /files/ { proxy_pass http://127.0.0.1:5100/files/; }
    location /health { proxy_pass http://127.0.0.1:5100/health; }

    # Everything else → Next.js (:3000)
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
    }
}

🔥 実際にハマった10のポイントと解決策

ここからが本番です。ドキュメントに書いてない、やってみないとわからないトラブルの数々を共有します。

❶ pgvectorのshared_preload_librariesエラー

🚨 エラー

"vector" is not an allowed value for shared_preload_libraries

pgvectorはPostgreSQLのEXTENSIONであり、shared_preload_librariesに登録する共有ライブラリではありません。

# ❌ 間違い
parameter {
  name  = "shared_preload_libraries"
  value = "vector"
}

# ✅ 正解 — pgvectorはCREATE EXTENSIONで有効化するだけ
parameter {
  name  = "shared_preload_libraries"
  value = "pg_stat_statements"
}

❷ nginx proxy_passのパス消失問題

🚨 症状

/console/api/setup にアクセスすると / にリダイレクトされる
# ❌ パスが消える — /console/api/setup → / として転送
location /console/api/ {
    proxy_pass http://127.0.0.1:5100/;
}

# ✅ パスを保持 — /console/api/setup → /console/api/setup
location /console/api/ {
    proxy_pass http://127.0.0.1:5100/console/api/;
}

❸ Next.jsのバインドアドレス問題

🚨 症状

502 Bad Gateway — Next.jsは動いているのにnginxから到達できない

Next.jsスタンドアロンモードはデフォルトでコンテナの内部IP(172.17.0.2等)にバインドされ、127.0.0.1:3000ではリッスンしていません。

# supervisord.conf で HOSTNAME を指定
[program:web]
environment=PORT="3000",HOSTNAME="0.0.0.0",...

❹ Plugin Daemonの未ドキュメント環境変数 PLATFORM

🚨 エラー

Config.Platform: Field validation failed on the 'required' tag

PLATFORM環境変数が必須だが公式ドキュメントに記載なし。公式docker-compose.yamlにPLATFORM=localがありました。

# run-plugin-daemon.sh
export PLATFORM="local"
exec /app/plugin-daemon/main

❺ RDSのSSL接続必須

🚨 エラー

no pg_hba.conf entry for host "10.0.11.225", no encryption

AWS RDSはデフォルトでSSL接続を要求。DB_SSL_MODE=disableではpg_hba.confレベルで拒否されます。

export DB_SSL_MODE="require"   # ← "disable" ではダメ!

❻ supervisordの%(ENV_...)s展開が不安定

supervisord.confで%(ENV_PLUGIN_DAEMON_KEY)sのように環境変数を参照しようとしたが、展開されないケースがある。ラッパースクリプトでシェル変数展開するのが確実。

#!/bin/bash
# run-plugin-daemon.sh
export SERVER_KEY="${PLUGIN_DAEMON_KEY}"
export DIFY_INNER_API_KEY="${INNER_API_KEY_FOR_PLUGIN}"
export DB_SSL_MODE="require"
export PLATFORM="local"
exec /app/plugin-daemon/main

❼ dify_pluginデータベースの自動作成

Plugin Daemonはdify_pluginという別データベースを要求するが、RDSの初期状態にはない。コンテナ起動時に自動作成するスクリプトを追加。

#!/bin/bash
# init-db.sh
for i in $(seq 1 30); do
  pg_isready -h "$DB_HOST" -U "$DB_USERNAME" 2>/dev/null && break
  sleep 2
done

PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -U "$DB_USERNAME" \
  -d "$DB_DATABASE" \
  -c "SELECT 1 FROM pg_database WHERE datname='dify_plugin'" \
  | grep -q 1 || \
PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -U "$DB_USERNAME" \
  -d "$DB_DATABASE" -c "CREATE DATABASE dify_plugin;"

⚠️ 重要:バックグラウンド実行にすること

#!/bin/bash
# start.sh — エントリーポイント
/usr/local/bin/init-db.sh &   # バックグラウンド!
exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf

同期実行するとDB接続待ちでsupervisordの起動が遅延し、ヘルスチェックに失敗します。

❽ Plugin Daemonのlatestタグが存在しない

docker pull langgenius/dify-plugin-daemon:latest404。公式docker-compose.yamlでバージョンを確認:

curl -s https://raw.githubusercontent.com/langgenius/dify/main/docker/docker-compose.yaml \
  | grep plugin-daemon
# → langgenius/dify-plugin-daemon:0.5.3-local

❾ App Runner CREATE_FAILEDからの復旧

ECRにイメージがない状態でterraform applyするとCREATE_FAILEDに。この状態ではstart-deploymentができません。

# destroyして再作成するしかない
terraform destroy -target=module.apprunner.aws_apprunner_service.main
# ECRにイメージをpush後
terraform apply -target=module.apprunner.aws_apprunner_service.main

❿ Celery WorkerのRabbitMQデフォルト問題

🚨 エラー

Cannot connect to amqp://guest:**@127.0.0.1:5672//: Connection refused

Dify 1.xのCELERY_BROKER_URLのデフォルトがNoneで、Celeryのデフォルト(amqp://localhost)にフォールバックします。明示的にRedisを指定:

CELERY_BROKER_URL = "redis://${var.redis_host}:6379/1"
BROKER_USE_SSL    = "false"

デプロイ手順まとめ

# 1. Terraform で AWS リソース作成(約15分)
cd terraform/development
terraform init && terraform apply

# 2. Docker イメージビルド
cd ../../docker
docker build -t dify-allinone:latest .

# 3. ECR にプッシュ
aws ecr get-login-password --region ap-northeast-1 | \
  docker login --username AWS --password-stdin $ECR_URL
docker tag dify-allinone:latest $ECR_URL/dify-development:latest
docker push $ECR_URL/dify-development:latest

# 4. デプロイ
aws apprunner start-deployment --service-arn $SERVICE_ARN

# 5. 確認
curl https://$APP_RUNNER_URL/health
# → {"status": "ok", "version": "1.13.0"}

まとめ

  1. ステートレスとステートフルの分離 — アプリはコンテナに、データ層はAWSマネージドに
  2. App Runnerで運用負荷を最小化 — ALB不要、HTTPS自動、オートスケール組み込み
  3. Terraformで全インフラをコード管理 — 再現性と環境間の一貫性を確保
  4. supervisordによるAll-in-Oneコンテナ — App Runnerの1コンテナ制約をクリア
  5. 月額~$94 — 開発環境として十分に低コスト

ソースコードはGitHubで公開しています。

📝 教訓10選(再掲)

  1. pgvectorはshared_preload_libraries不要
  2. nginx proxy_passの末尾/に注意
  3. Next.jsはHOSTNAME=0.0.0.0必須
  4. Plugin DaemonのPLATFORM環境変数は必須(ドキュメントなし)
  5. RDSはSSL必須 — DB_SSL_MODE=require
  6. supervisordの%(ENV_...)sは不安定 — ラッパースクリプトで
  7. dify_pluginデータベースは自動作成(バックグラウンドで)
  8. dify-plugin-daemonにlatestタグなし
  9. App Runner CREATE_FAILEDは再デプロイ不可
  10. CeleryブローカーはデフォルトRabbitMQ — 明示的にRedis指定

この記事が役に立ったらシェアしてください

DifyのAWSデプロイで同じように悩んでいる方に届きますように 🙏

カテゴリ

AWS・クラウド

公開日

2026-03-06

💬 無料技術相談のご案内

この記事でご紹介した技術について、導入や活用のご相談を30分間無料承っております。

  • 「自社でもDifyをAWSで運用できる?」といった技術的な疑問
  • 既存システムとの連携・インフラ移行に関するご相談
  • コスト感や導入スケジュールの目安

30年以上のIT経験をもとに、率直にお答えします。強引なセールスや勧誘は一切ありません。

野口真一 野口真一

お気軽にご相談ください

DifyやAIプラットフォームのAWSデプロイ、インフラ構築のご相談など、お気軽にお問い合わせください。