🤖

さくらVPSでDifyをセルフホスト!社内ナレッジ×AIボット×Slack連携の完全構築ガイド(第1回:環境構築編)

に公開

背景と目的

IT統制グループの穗滿です。IT統制グループには、いわゆる情シスの ITシステムチーム があります。
弊社には全国に300店舗のリラクゼーション店舗があるのですが、ITシステムチームには毎日似た様な問い合わせがしばしば発生します。マニュアル等をつくっても聞くのが早いから聞く、というケースもあるかもしれません。
この時間が誰にとっても非常にもったいないため、社内ナレッジから一次対応をしてくれるAIボットくんを構築します。

この記事は長くなるため全2回を想定しており、今回は1回目です。

今回は環境構築がメインとなります。
次回は、実際にSlack連携するAIボットを呼び出して利用できる方法を紹介します。

環境概要

Dify(ディフィ)

ナレッジをもとにAIボットを作れるプラットフォームとして Dify というものがあり書籍等の情報比較的多いため、今回は Dify を利用しようと思います。ただ、SaaS版が思いの外高く(月9,000円程度)、Difyにはオープンソース版があったので、セルフホスティングしたいと思います。

Difyの利用方法については、弊社の寺本さんの記事も参考にしてください。
https://zenn.dev/medirom_tech/articles/93e962fcaa2551

サーバー:さくらのVPS

本格運用前のPoC的な位置付けもあり、まずはコスト的に気軽に試せるさくらのVPSにDocker環境を構築する形とします。将来的にAWS等のクラウドで本格運用したくなっても、Docker環境であれば比較的移設しやすいだろうという目論見です。
メモリやリージョンで費用が異なるので、お好みで選びましょう。
今回はメモリ4GBで様子を見ることにしました。

OS:Ubuntu 24.04. amd64

2029年6月まで標準のセキュリティメンテナンスが提供される 24.04を選択しました。

ドメイン

オープンソース版DifyはSaaS版のようにマルチテナント運用はできなさそうだったため、1プロジェクトに1Difyという形を想定します。そのため、サブドメイン運用を前提とします。
今回は例として、proj-a.dify.example.com というドメインとします。

その他のプラットフォームを試す可能性がある

Dify以外にも、n8nやpsonoなど別のプラットフォームサービスでDockerイメージが提供されているものがたくさんあるので、同じVPS上でPoCを実施する可能性があります。同じVPS上で Dify・n8n・psono、etc... を仲良く動かすなら、共通のリバースプロキシ(Traefik、Caddyなど)でHTTPS/TLSを一元管理し、各アプリは個別のCompose(プロジェクト)として分けて起動し、共通の外部Dockerネットワークでつなぐのが運用しやすくて安全だと考えました。

例えばTraefik採用を前提にした場合、4GB RAM のVPS向けに最小~実務運用で困らない構成を検討します。

ディレクトリ構成(例)

/opt
├── traefik/                       # リバースプロキシ & TLS管理
│   ├── docker-compose.yml         # Traefik定義(80/443待受、ACME設定)
│   ├── traefik.yml                # 静的設定
│   └── letsencrypt/
│       └── acme.json              # 証明書キャッシュ(600権限)

├── dify-projA/                    # プロジェクトA用 Dify インスタンス
│   ├── docker-compose.yml         # Dify本体のサービス定義(nginx/api/web/worker/DBなど)
│   ├── .env                       # 環境変数設定
│   ├── nginx/
│   │   └── template/              # Dify同梱のnginxテンプレート
│   └── volumes/                   # 永続化データ
│       ├── storage/               # api/worker共有ストレージ
│       ├── db/                    # PostgreSQL永続化
│       └── redis/                 # Redis永続化

├── dify-projB/                    # プロジェクトB用 Dify インスタンス
│   ├── docker-compose.yml
│   ├── .env                       
│   ├── nginx/
│   │   └── template/
│   └── volumes/
│       ├── storage/
│       ├── db/ (任意)
│       └── redis/ (任意)

├─ n8n/                            # 将来的な構想(今回⁨⁩は範囲外)
│   └─ docker-compose.yml

└── backups/                       # バックアップ格納用:定期的に pg_dump、rsync したファイルを置く想定
    ├── postgres/                  # pg_dump結果
    ├── storage/                   # ファイルコピー
    └── traefik/                   # acme.json退避

手順

01.DNSの設定を済ませておく

この手順の詳細は割愛します。
利用中のDNSで A proj-a.dify.example.com -> さくらVPSのグローバルIPアドレス を作成

反映確認(ローカルから)
dig +short proj-a.dify.example.comさくらVPSのグローバルIPアドレス が返ることを確認しましょう。

02.さくらのVPSを契約し、Docker実行環境を選ぶ

  • さくらのVPSコンパネで、OSインストールを実施します。(Ubuntu 24.04. amd64)

  • 下記は設定例です。内容が決定したら、[内容確認] をクリックしてください。

  • [OS再インストール](1回目だとインストール)をクリックする

  • 画面右下に上図のような表示がでる。完了するまで待つ。

ここまで来たら、SSHかコンソールでサーバーにログインします。

03.Ubuntu 初期設定

# パッケージ管理のアップデート、アップグレードして最新化
sudo apt update && sudo apt -y upgrade

# 基本ツールインストール
sudo apt -y install curl git jq ca-certificates gnupg lsb-release unzip

# タイムゾーン設定
sudo timedatectl set-timezone Asia/Tokyo

# スワップ追加 (2GB)
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# Redis向け調整(接続待ち)
echo 'net.core.somaxconn=1024' | sudo tee /etc/sysctl.d/99-dify.conf
sudo sysctl --system

04.Docker / Compose のインストール

# Docker公式リポジトリ登録
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# sudoなしでdockerを使えるように
sudo usermod -aG docker $USER
newgrp docker

# Dockerインストール確認
docker version
docker compose version

05.共通ネットワークの作成

各サービスをリバースプロキシでまとめるため、外部ネットワークを作ります。このコマンドを実行すると、コンテナがこのネットワークに接続できるようになり、コンテナ間の通信を容易にすることができるようになります。

docker network create web

06.Traefik (リバースプロキシ+Let’s Encrypt)

sudo mkdir -p /opt/traefik/letsencrypt/
cd /opt/traefik/letsencrypt/
sudo touch acme.json && sudo chmod 600 acme.json
cd ../
sudo vi docker-compose.yml

/opt/traefik/docker-compose.yml をつくる

services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    command:
      # プロバイダ: Dockerを読む
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      # エントリポイント
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      # ACME(Let's Encrypt, HTTP-01)
      - --certificatesresolvers.le.acme.email=<管理に都合が良いメールアドレス>
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
      # ログ(任意)
      - --accesslog=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - web

networks:
  web:
    external: true
    name: web

起動します

cd /opt/traefik
docker compose up -d

Dify (OSS版)

# テナント用ディレクトリ
sudo mkdir -p /opt/dify-projA
sudo chown -R $USER:$USER /opt/dify-projA
cd /opt/dify-projA
git clone https://github.com/langgenius/dify.git
cd dify/docker
mkdir -p volumes/{storage,db,redis,weaviate}
sudo cp .env.example .env
sudo vi .env

.env の一部抜粋

APP_WEB_URL=https://proj-a.dify.example.com
APP_API_URL=https://proj-a.dify.example.com
SERVICE_API_URL=https://proj-a.dify.example.com
CONSOLE_WEB_URL=https://proj-a.dify.example.com
CONSOLE_API_URL=https://proj-a.dify.example.com

SECRET_KEY=<<openssl rand -base64 42>>
MIGRATION_ENABLED=true

STORAGE_TYPE=local

VECTOR_STORE=weaviate

docker-compose.yaml の nginx サービスに Traefikラベルとnetworksを追加:

# /opt/dify-projA/dify/docker
sudo vi docker-compose.yaml
labels:
  - traefik.enable=true
  - traefik.docker.network=web
  - traefik.http.routers.dify-proj-a.rule=Host(`proj-a.dify.example.com`)
  - traefik.http.routers.dify-proj-a.entrypoints=websecure
  - traefik.http.routers.dify-proj-a.tls=true
  - traefik.http.routers.dify-proj-a.tls.certresolver=le
  - traefik.http.routers.dify-proj-a-web.rule=Host(`proj-a.dify.example.com`)
  - traefik.http.routers.dify-proj-a-web.entrypoints=web
  - traefik.http.routers.dify-proj-a-web.middlewares=dify-proj-a-redirect
  - traefik.http.middlewares.dify-proj-a-redirect.redirectscheme.scheme=https
  - traefik.http.services.dify-proj-a.loadbalancer.server.port=80
networks:
  - default
  - web

※nginxのports: の設定はコメントアウトか削除をしておく

    #ports:
    #    - "${EXPOSE_NGINX_PORT:-80}:${NGINX_PORT:-80}"
    #    - "${EXPOSE_NGINX_SSL_PORT:-443}:${NGINX_SSL_PORT:-443}"

複数のdifyのdockerを立ち上げるときに5003ポートの干渉が起きることがあるので、plugin_daemon:port:expose に書き換える。

plugin_daemon:
~~~色々記述がある~~~~
  #port:
  expose:
    - "${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003}:${PLUGIN_DEBUGGING_PORT:-5003}" 

docker-compose.yaml の末尾あたりの networks: に webを追加

networks:
  web:
    external: true

起動します

docker compose pull
docker compose -p dify-proj-a up -d

07.Difyインストール

WEBブラウザで以下のURLにアクセスして、イストールしてください。

https://設定ドメイン/install

08.Difyログイン

先ほど登録したアカウントでログインしましょう。

09.データの永続化

この構成では、Difyも各ミドルウェアもホスト側ディレクトリ(bind mount)に永続化しているため、コンテナの再起動・再作成してもデータは残ります。Redisに関しては、Dify では --appendonly yes にしているため、すべての書き込み操作をAOFファイルに追記し、再起動時に復元できます。

基準ディレクトリ:/opt/dify-teamA/dify/docker/
(以下に記すの相対パスは、基準ディレクトリを起点にした相対パスです)

コンポーネント コンテナ内パス ホスト側の永続化パス(=残る場所) 中身の例
Dify API/Worker(ファイル類) /app/api/storage ./volumes/storage/ 添付・ナレッジ・生成物など
PostgreSQL /var/lib/postgresql/data ./volumes/db/ DB本体(WAL/データファイル)
Redis(AOF有効) /data ./volumes/redis/ 内部ジョブキュー(非同期処理キュー)+一部セッション管理のバックエンド
Weaviate(Vector DB) /var/lib/weaviate ./volumes/weaviate/ 「Knowledge Base機能」「RAG」を支えるベクトル検索エンジン
Traefik(証明書)※別ディレクトリ /letsencrypt/acme.json /opt/traefik/letsencrypt/acme.json Let’s Encrypt 証明書キャッシュ

どういう時にデータが残るのか、消えるのか

  • docker compose restart データは残る(マウント先は同じ)
  • docker compose down データは残る(コンテナを消しても、bind mount の元ディレクトリは消えない)
  • docker compose down -v データは消える(今回のような bind mount のホスト側の実ディレクトリは消えないが、Dockerが管理している名前付きボリュームは削除されるので永続化が失われる)
  • ホスト側ディレクトリを中身丸ごと削除した場合 当然消える(=バックアップ対象)
  • docker system prune -a イメージ・未使用リソースは消すが、bind mount のディレクトリは残る(ただし、--volumesオプションをつけると永続データも全て削除される)

10.バックアップ&リストア

ここでは詳細は割愛して、データ系のバックアップとリストアの方法を記載します。

バックアップ

sudo mkdir -p /opt/backups/{postgres,storage/projA,weaviate/projA,redis/projA}
cd /opt/dify-projA/dify/docker

# 1) PostgreSQL(論理バックアップ)
docker exec -t <<DB名>> pg_dump -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -F c \
  > /opt/backups/postgres/dify_proj_a_$(date +%F).dump

# 2) ストレージ(ファイル)
rsync -a ./volumes/storage/ /opt/backups/storage/projA/

# 3) Weaviate
rsync -a ./volumes/weaviate/ /opt/backups/weaviate/projA/

# 4) Redis(AOF)
rsync -a ./volumes/redis/ /opt/backups/redis/projA/

リストア

# Postgres(DBは空のコンテナが立っている前提で)
pg_restore -h <host> -U <user> -d <db> -c -j 4 /opt/backups/postgres/dify_proj_a_YYYY-MM-DD.dump

# ストレージ/Weaviate/Redis は停止した状態でディレクトリを戻す
docker compose -p dify-proj-a down
rsync -a /opt/backups/storage/projA/ ./volumes/storage/
rsync -a /opt/backups/weaviate/projA/ ./volumes/weaviate/
rsync -a /opt/backups/redis/projA/ ./volumes/redis/
docker compose -p dify-proj-a up -d

第1回はここまで

今回は環境づくりのところを中心に記事にしました。
セルフホスティングできる環境が整うと、いろいろ試したくなってきますよね。
次回は、いよいよAIボットを実際に使える様にするところまで紹介します。


採用情報

メディロムグループでは以下のようなサービスを展開しています。

  • 全国300店舗以上のリラクゼーションスタジオ「Re.Ra.Ku」
  • 世界初!充電不要の活動量計「MOTHER bracelet」
  • ヘルスケアコーチングアプリ「Lav」

健康って何をするにも大事ですよね。
健康でなければ楽しみも半減し、仕事も思う様にはいかないです。
メディロムグループでは健康・ヘルスケア領域に興味があるエンジニア、PMを絶賛募集中です!
効果がダイレクトにわかる自社サービスをグロースさせながら、ご自身の成長も目指されてみませんか?

https://medirom.co.jp/recruit

メディロムグループ Tech Blog

Discussion