さくらVPSでDifyをセルフホスト!社内ナレッジ×AIボット×Slack連携の完全構築ガイド(第1回:環境構築編)
背景と目的
IT統制グループの穗滿です。IT統制グループには、いわゆる情シスの ITシステムチーム
があります。
弊社には全国に300店舗のリラクゼーション店舗があるのですが、ITシステムチームには毎日似た様な問い合わせがしばしば発生します。マニュアル等をつくっても聞くのが早いから聞く、というケースもあるかもしれません。
この時間が誰にとっても非常にもったいないため、社内ナレッジから一次対応をしてくれるAIボットくんを構築します。
この記事は長くなるため全2回を想定しており、今回は1回目です。
今回は環境構築がメインとなります。
次回は、実際にSlack連携するAIボットを呼び出して利用できる方法を紹介します。
環境概要
Dify(ディフィ)
ナレッジをもとにAIボットを作れるプラットフォームとして Dify
というものがあり書籍等の情報比較的多いため、今回は Dify
を利用しようと思います。ただ、SaaS版が思いの外高く(月9,000円程度)、Difyにはオープンソース版があったので、セルフホスティングしたいと思います。
Difyの利用方法については、弊社の寺本さんの記事も参考にしてください。
サーバー:さくらの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にアクセスして、イストールしてください。
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を絶賛募集中です!
効果がダイレクトにわかる自社サービスをグロースさせながら、ご自身の成長も目指されてみませんか?
Discussion