【Dify】AWSのEC2にDify OSS版構築してみた

2025/02/11に公開

はじめに

こんにちは!

今回は「Dify」というサービスを紹介します。Dify は、簡単に LLM(大規模言語モデル)を活用したワークフローを構築できるプラットフォームで、直感的な UI で 生成AIを使用したアプリケーションを作成・運用できるサービスです。一般的には SaaS 版を利用する方が多いですが、実は OSS 版 も公開されており、自前の環境にホスティングして独自にカスタマイズしたり、プライベート環境で運用することも可能です。

OSS 版を自前でホスティングするメリットには、独自の LLM や機能をカスタマイズできること、作成したAPIを保護することができます。
そこで今回は、CloudFormation を活用して AWS の EC2 上に Dify(OSS 版)を構築 し、そのままでは招待のためのメール送信ができなかったため SMTP 設定 を適用する方法もあわせて解説します。

想定する読者

  • AWS を使ってDIfyを自分の環境でホスティングしたい人
  • EC2でホスティングする際にIP制限をかけたい人

今回の目的

  • EC2 上に Dify(OSS 版)をデプロイする。
  • パブリック IP で誰でもアクセスできないように、ALB 経由で IP 制限かける
  • Route 53 と ACM を使って独自ドメインを割り当て、HTTPS 通信を実現する

全体の流れ

  1. CloudFormation(CF)でインフラ構築
    • CFのテンプレートを使用して、VPC、サブネット、インターネットゲートウェイ、EC2インスタンスなどを一括で作成する。
    • EC2にGit、Docker、Docker Composeをインストールし、Dify(OSS版)をデプロイする。
  2. ALB(ロードバランサ)でIP制限
    • ALBを構築し、HTTP/HTTPSリスナーを設定する。
    • ALBのセキュリティグループで、許可する接続元IP(CIDR)のみアクセスを許可する。
    • EC2のセキュリティグループでは、ALBからのアクセスのみ許可し、直接のアクセスを遮断する。
  3. SMTP設定でメール送信を有効化
    • /opt/dify/docker ディレクトリ内の .env に、SMTPサーバーの情報を追記する。
    • docker-compose を再起動して環境変数を反映する。
    • 招待メールやパスワードリセットメールが正常に送信されるか確認する。
  4. Route 53とACMで独自ドメイン & HTTPS設定
    • AWS Certificate Managerでドメイン証明書を取得。
    • ALBのHTTPSリスナーに証明書を割り当て、暗号化通信を設定する。
    • Route 53でAレコードをALBに向けて設定し、独自ドメインでアクセスできるようにする。

構築

今回は、EC2 のホスティングまでは AWS のワークショップを参考に進めました。興味がある方はぜひご覧ください。 AWSワークショップ

1. CloudFormation でのリソース作成

AWS のワークショップにある yaml ファイルをダウンロードし、セットアップを行いました。
ダウンロードはこちら

最新版のダウンロードはこちらから
AWSTemplateFormatVersion: '2010-09-09'
Description: Dify CloudFormation Template (uksb-igk3zskei3)

Parameters:
  VpcCIDR:
    Type: String
    Default: 192.168.0.0/16
    Description: CIDR block for the VPC

  Subnet1CIDR:
    Type: String
    Default: 192.168.0.0/20
    Description: CIDR block for Subnet 1

  Subnet2CIDR:
    Type: String
    Default: 192.168.16.0/20
    Description: CIDR block for Subnet 2

  AllowedCIDR:
    Type: String
    Default: 0.0.0.0/0
    Description: CIDR block to allow HTTP traffic from

  AmazonLinuxAMI:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64

Resources:
  DifyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: dify-vpc

  Subnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref DifyVPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: !Ref Subnet1CIDR
      Tags:
        - Key: Name
          Value: dify-subnet-1

  Subnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref DifyVPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: !Ref Subnet2CIDR
      Tags:
        - Key: Name
          Value: dify-subnet-2

  InternetGateway:
    Type: AWS::EC2::InternetGateway

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref DifyVPC
      InternetGatewayId: !Ref InternetGateway

  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref DifyVPC

  Route:
    Type: AWS::EC2::Route
    DependsOn: VPCGatewayAttachment
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  Subnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet1
      RouteTableId: !Ref RouteTable

  Subnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet2
      RouteTableId: !Ref RouteTable

  DifySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP traffic
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: !Ref AllowedCIDR
          Description: Allow HTTP traffic from allowed CIDR
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic
      VpcId: !Ref DifyVPC
      Tags:
        - Key: Name
          Value: dify-sg

  DifyWsInstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AmazonBedrockFullAccess'
        - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref DifyWsInstanceRole

  DifyWsInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref AmazonLinuxAMI
      InstanceType: t3.medium
      NetworkInterfaces:
        - AssociatePublicIpAddress: 'true'
          DeviceIndex: '0'
          GroupSet:
            - !Ref DifySecurityGroup
          SubnetId: !Ref Subnet1
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 20
            VolumeType: gp2
            Encrypted: 'true'
      Tags:
        - Key: Name
          Value: dify-ws
      IamInstanceProfile: !Ref InstanceProfile
      UserData:
        Fn::Base64: |
          #!/bin/bash
          max_attempts=5
          attempt_num=1
          success=false
          while [ $success = false ] && [ $attempt_num -le $max_attempts ]; do
            sudo dnf install -y git docker
            if [ $? -eq 0 ]; then
              echo "dnf install succeeded"
              success=true
            else
              echo "dnf install $attempt_num failed. trying again..."
              sleep 3
              ((attempt_num++))
            fi
          done

          sudo systemctl start docker
          sudo gpasswd -a ec2-user docker
          sudo gpasswd -a ssm-user docker
          sudo chgrp docker /var/run/docker.sock
          sudo service docker restart
          sudo systemctl enable docker
          sudo curl -L "https://github.com/docker/compose/releases/download/v2.28.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
          sudo chmod +x /usr/local/bin/docker-compose
          sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
          cd /opt
          sudo git clone https://github.com/langgenius/dify.git
          cd /opt/dify
          sudo git checkout v0.15.3
          sudo git pull origin v0.15.3
          cd /opt/dify/docker
          sudo cp .env.example .env
          docker-compose up -d

Outputs:
  InstancePublicIP:
    Description: Public IP of the EC2 instance
    Value: !GetAtt DifyWsInstance.PublicIp
    Export:
      Name: DifyInstancePublicIP-new
  InstanceId:
    Description: InstanceId of the EC2 instance
    Value: !Ref DifyWsInstance
    Export:
      Name: DifyInstanceId-new

次に AWS コンソールの検索バーに「CloudFormation」と入力して開きます。

CloudFormation の画面で「スタックの作成」を選び、新しいリソースを作成します。

その後、スタック作成画面で「テンプレートを使用する」を選択し、先ほどダウンロードした yaml ファイルをアップロードします。

今回は、その他の設定はデフォルトのまま進めました。

リソースの作成には少し時間がかかりますが、ステータスが CREATE_COMPLETE になったら作成完了です。

実際に EC2 のページに移動し、リソースが作成されていることと、該当インスタンスのパブリック IP をブラウザで開いて Dify のページが表示されることを確認してください。

2. ALB 経由で IP 制限をかける

このままでは、パブリック IP を知っていれば誰でもアクセスできる状態です。
そこで、Dify に直接 EC2 インスタンスのパブリック IP でアクセスさせるのではなく、ALB (Application Load Balancer) を挟んで接続元の IP アドレスや CIDR ブロックに応じてアクセスを制限させたいと思います。

2.1 ALB の作成

まず、EC2 のページで左メニューから Load Balancers を選択し、ALB (Application Load Balancer) を作成します。

  1. リスナーを作成
    • HTTP (ポート 80) や HTTPS (ポート 443) のリスナーを作成
    • 今回は両方とも作成し、HTTP にアクセスがあった場合は HTTPS にリダイレクトするように設定しました
詳しい HTTP→HTTPS のリダイレクト方法


HTTP のリスナー設定で

  • URL にリダイレクト
    • URL には HTTPS (443) を指定

とすることで、HTTP にアクセスが来た場合に自動的に HTTPS に転送できます。

  1. ターゲットグループを作成

    • ターゲットタイプを「インスタンス」または「IP」に設定
    • Dify を動かしている EC2 インスタンスを登録
  2. ALB のセキュリティグループを作成

    • インバウンドルールで、アクセスを許可する IP アドレスや CIDR ブロックのみに制限
      • 例:xxx.x.xxx.x/32 など
    • AllowedCIDR といった特定範囲からのアクセスのみを許可
  3. EC2 のセキュリティグループを変更

    • EC2 側でのインバウンドルールは「ALB のセキュリティグループからの通信のみ許可」する
    • これにより、直接インターネットから EC2 インスタンスへアクセスできなくなります

2.2 セキュリティグループの設定

  • ALB セキュリティグループ (ALBSecurityGroup)
    • インバウンドで AllowedCIDR など特定の範囲のみ許可
    • アウトバウンドで EC2 インスタンスのセキュリティグループを許可
  • EC2 インスタンス側セキュリティグループ
    • インバウンドルールを「ALB のセキュリティグループからの通信のみ許可」に変更
    • これで、ALB を経由しない直接アクセスが拒否されます

2.3 動作確認

  1. ブラウザでALB の DNS 名にアクセスする
  2. AllowedCIDR 外の IP からのアクセスは拒否(タイムアウトや 403 など)
  3. AllowedCIDR 内の IP からのアクセスで Dify のページが表示される

上記が確認できればしっかりとIP制限がかかっています!!

3. メール送信の有効化

3.1 SMTP 設定を .env に追記

Dify から招待メールやパスワードリセットメールを送るには、SMTP サーバーの設定が必要です。
/opt/dify/docker ディレクトリにある .env ファイルを編集し、以下のように書き換えます(今回は Gmail を利用する場合)。

まずは.envファイルの中身を確認します。

cd /opt/dify/docker
cat .env | grep SMTP
MAIL_TYPE=smtp
MAIL_DEFAULT_SEND_FROM=yourname@example.com
SMTP_PORT=587
SMTP_SERVER=smtp.gmail.com
SMTP_USERNAME=yourname@example.com
SMTP_PASSWORD=xxxxxxxxxxxxxxxx
SMTP_USE_TLS=true
SMTP_OPPORTUNISTIC_TLS=true

独自ドメインのメールサーバーをお持ちの場合は、その情報を入力してください。

GoogleMailを使用する方法
  1. Googleのアカウント設定から、アプリパスワードを作成する

3.2 コンテナの再起動

.env を変更したら、コンテナを再起動して設定を反映します。

cd /opt/dify/docker
docker-compose down
docker-compose up -d --force-recreate

4. 動作確認

設定が完了したら、Dify の管理画面またはコンソールからユーザー招待メールを送信してみて、正常にメールが届くかを確認します。
もしメールが届かない場合、以下をチェックしてみてください。

  1. SPF, DKIM, DMARC 設定
    • Gmail や独自ドメインで認証が必要なケースがあります
  2. 迷惑メールフォルダ
    • テストとして自分宛に送信し、迷惑メールに分類されていないか確認してみましょう

5. ドメイン設定 (Route 53 + ACM の例)

ここまでで、ALB を経由して IP 制限を行い、EC2 で Dify を動かす環境は整いましたが、実際には独自ドメインを利用してアクセスするケースが多いと思われます。そこで、AWS の Route 53 と AWS Certificate Manager (ACM) を使用して独自ドメインを割り当て、HTTPS 化する手順を簡単に紹介します。

5.1 ACM で SSL/TLS 証明書を取得

  1. AWS コンソールから「Certificate Manager」を開く
  2. 「証明書のリクエスト」から新しい証明書をリクエストする
  3. Route 53 を利用している場合、DNS レコードを自動で作成できますので今回はこちらの機能を使用いたしました!

5.2 ALB に証明書を割り当て

  1. ALB のリスナー設定(HTTPS:443 のリスナー)を開く
  2. 「リスナーの編集」で、証明書の設定画面があります
  3. 先ほどリクエストした証明書(ACM の ARN)を選択し、HTTPS 通信を暗号化します

5.3 Route 53 でドメインを ALB に向ける

  1. Route 53 のホストゾーンで、自分のドメインを選択
  2. 「レコードを作成」から A レコード(ALIAS)を ALB の DNS 名へ向ける
    例:example.com → ALB の DNS 名 (xxxx.elb.amazonaws.com)
  3. www.example.com 用にも同様に A レコードを作成し、ALB に向ける
  4. 数分~数十分待つと、https://example.com でアクセスできるようになります

最後に

今回行った

  • CloudFormation (CF) を利用し、VPC、EC2、Docker、Dify の構築をまとめて自動化
  • 独自ドメインや HTTPS 化が必要な場合は、Route 53 と ACM を利用して設定を行う

これらの構築をすることでよりセキュリティ性の高い状態でDifyを使用することができます!!

Dify はOpenAI API などのLLMのAPI をすぐに使える管理画面を備えているためLLM アプリケーションをスピーディに構築したい方に特におすすめです!!ぜひご活用ください!!

Solvio株式会社

Discussion