【徹底解説】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ベースなので問題なく動作します。

App Runnerのネットワーク構成 — なぜプライベートサブネットにつながるのか

「プライベートサブネットにRDSやRedisを置いているのに、App Runnerからどうやってアクセスしているのか?」という疑問を持つ方も多いと思います。ここがApp Runnerの最もわかりにくいポイントです。

App Runnerはあなたのサブネットに「いない」

実はApp Runnerのコンテナはあなたのサブネットの中で動いていません。AWSが管理する専用のネットワーク領域で実行されています。

インターネット(ユーザーのブラウザ)
    │
    ▼
┌─────────────────────────────────────┐
│  AWS管理領域(App Runner)             │
│                                     │
│  ・組み込みALB(HTTPS終端)          │
│  ・コンテナ実行環境                  │
│  ・オートスケール                    │
│                                     │
│  VPC Connector ──────────┐        │
└──────────────────────────│────────┘
                           │
                           ▼
┌─────────────────────────────────────┐
│  あなたのVPC                         │
│  ┌─────────────────────────────┐    │
│  │ プライベートサブネット        │    │
│  │  ・RDS(PostgreSQL 16)      │    │
│  │  ・ElastiCache(Redis 7.1)  │    │
│  │  ・S3 VPCエンドポイント      │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

VPC Connectorの役割

VPC Connectorは、App Runnerのコンテナからあなたのプライベートサブネットへの片方向の接続トンネルを作ります。

  • App Runner → プライベートサブネット:接続できる(RDS、Redisにアクセス可能)
  • プライベートサブネット → App Runner:直接は接続できない
  • インターネット → App Runner:AWSの組み込みALB経由で接続(自動HTTPS)

つまり、RDSやRedisは完全にプライベート(インターネットから到達不能)に保ちつつ、App Runnerだけがアクセスできるという安全な構成になっています。

負荷分散の仕組み

App RunnerにはALB(ロードバランサー)が最初から組み込まれています。ECSのように自分でALBを作成・設定する必要がありません。

ユーザーA ──┐
ユーザーB ──┤→ App Runner ALB ─→ コンテナ1(1 vCPU / 2GB)
ユーザーC ──┤    (自動HTTPS)  ─→ コンテナ2(1 vCPU / 2GB)
ユーザーD ──┘                  ─→ コンテナ3(1 vCPU / 2GB)
機能動作
HTTPS終端自動でACM証明書を発行・更新。設定不要
オートスケールリクエスト数に応じて min_instancesmax_instances の間で自動増減
ヘルスチェック/health を10秒間隔でチェック。応答しないコンテナは自動置換
ゼロスケールmin_instances=0 にすればリクエストがないときはコンテナ停止(コスト削減)

今回の設定では:

  • development:1〜2インスタンス(通常は1台で十分)
  • production:1〜4インスタンス(負荷に応じて自動スケール)

ECS Fargateとの比較

App RunnerECS Fargate
ALB組み込み(無料)自分で作る(~$20/月)
HTTPS自動ACM + ALBで自分で設定
コンテナの場所AWS管理領域あなたのVPC内
DBアクセスVPC Connector経由同一VPC内で直接
Terraformリソース数少ない(~10)多い(~25)
サイドカー不可可能
柔軟性低い高い

App Runnerは「ALB込みのマネージドコンテナ」です。面倒なネットワーク設定をAWSに丸投げできる代わりに、1コンテナ制約がある。だからsupervisordでAll-in-Oneコンテナにした、というのが本構成の設計判断です。

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

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

なぜ分離が必要なのか

公式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

          # ... 中略(Redis, Celery, S3, pgvector, Plugin Daemon 等 30+ の環境変数) ...
          # ※ 完全なソースコードは下記noteで公開中
        }
      }
    }
    auto_deployments_enabled = false
  }

  # ... 中略(network_configuration, health_check_configuration) ...
}

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

# ... 中略(nginx, supervisord, postgresql-client インストール) ...
# ... 中略(Web frontend, Plugin daemon コピー) ...
# ... 中略(設定ファイル・スクリプト配置) ...
# ※ 完全なDockerfileは下記noteで公開中

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"

[program:worker]
# ... 中略 ...

[program:web]
command=node /app-web/web/server.js
environment=PORT="3000",HOSTNAME="0.0.0.0", ...

# ... 中略(plugin-daemon, nginx セクション) ...
# ※ 完全な設定ファイルは下記noteで公開中

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 等) ...
    }

    # ... 中略(/api/, /v1/, /files/, /health) ...

    # Everything else → Next.js (:3000)
    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}
# ※ 完全なnginx.confは下記noteで公開中

🔥 実際にハマった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"}

📦 すぐ構築できるTerraformソースコード全文

本記事で解説した Terraform 全7モジュール(24ファイル)+ Docker関連6ファイル の完全なソースコードを公開しています。

コピー&ペーストで terraform apply 一発。環境構築にかかる試行錯誤の時間を節約できます。

noteでソースコードを入手する(380円)

※ note.comのアカウントが必要です

まとめ

  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デプロイ、インフラ構築のご相談など、お気軽にお問い合わせください。