OllamaとMinIOでローカルS3互換ストレージのRAGを構築する方法|オブジェクトストレージに蓄積した社内文書をローカルLLMで検索・質問応答する手順

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > ローカルLLM > OllamaとMinIOでローカルS3互換ストレージのRAGを構築する方法|オブジェクトストレージに蓄積した社内文書をローカルLLMで検索・質問応答する手順
「社内文書をオブジェクトストレージに集約しているが、ローカルLLMのRAGとどう連携すればいいかわからない」「MinIOはすでに動いているのに、OllamaのEmbeddingと接続する手順が見つからない」

そんな悩みを抱えるインフラエンジニア・情シス担当者は多いはずです。MinIOはS3互換のオープンソースオブジェクトストレージで、社内ドキュメントの集約先として広く使われています。一方、Ollamaはローカルでテキスト埋め込み(Embedding)を生成できるため、MinIOとの組み合わせは「完全オンプレRAGシステム」を実現する有力な選択肢です。この記事では、MinIOのインストールからバケット設定・PythonでのEmbeddingパイプライン構築・Chromaへのベクトルデータ格納・RAG質問応答の実装まで、Ubuntu Server上で一気通貫に手順を解説します。すべてのデータとモデルがオンプレに留まる構成なので、機密性の高い社内文書にも安心して適用できます。

この記事のポイント

・nomic-embed-textでMinIO上のドキュメントをEmbeddingに変換しChromaに格納する
・MinIOのPython SDKでオブジェクトを動的に取得してRAGパイプラインを構築する
・MinIOのイベント通知でドキュメント追加を自動検知してインデックスを更新する
・すべてのデータとモデルがオンプレに留まる完全ローカルRAGを実現する


OllamaとMinIOでローカルS3互換ストレージのRAGを構築する方法|オブジェクトストレージに蓄積した社内文書をローカルLLMで検索・質問応答する手順

「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜMinIOとOllamaを組み合わせるのか

MinIOは単一バイナリで動作するS3互換のオブジェクトストレージです。AWSのS3と同じAPIを持つため、既存のS3クライアントライブラリ(boto3など)をそのまま使えます。社内ドキュメントをMinIOに集約しておけば、部門ごとのバケットに分類したファイルをOllamaのRAGシステムが読み込む構成が自然に成立します。

よくある構成のイメージを整理します。技術部門のWikiドキュメントをMinIOのtech-wikiバケットに保存しておき、社員がチャット画面から「サービスの障害対応手順は?」と質問すると、Ollamaが該当ドキュメントを検索して回答する形です。文書が追加されるたびにインデックスを更新すれば、常に最新の社内ナレッジに基づいた回答が得られます。

クラウドのS3やAzure Blob Storageとの決定的な違いは、すべてのデータがオンプレに留まる点です。機密性の高い社内文書をクラウドに送らずにRAGを運用できることが、情シス部門にとっての最大のメリットです。この背景については「社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢」でも詳しく解説しています。

今回構築するシステムの全体像はシンプルです。MinIOがドキュメントを保管し、PythonスクリプトがMinIOからファイルを取得してOllamaのEmbeddingを生成し、Chromaにベクトルとして格納します。RAG質問応答時はChromaで類似検索を行い、取得したコンテキストをOllamaのLLMに渡して回答を生成します。この一連のパイプラインがすべてローカルで完結します。

前提環境の確認とMinIOのインストール

この手順はUbuntu Server 22.04 LTS(または24.04 LTS)を前提とします。Ollamaがすでに起動してポート11434で動作している状態から始めます。Ollamaのインストール手順は「Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド」を参照してください。

まず現在の環境を確認します。

# Ollamaの動作確認 $ systemctl status ollama * ollama.service - Ollama Service Loaded: loaded (/etc/systemd/system/ollama.service; enabled) Active: active (running) since ... # ポートの待ち受け確認 $ ss -tlnp | grep 11434 LISTEN 0 4096 127.0.0.1:11434 0.0.0.0:* users:(("ollama",...))

1. Embedding用モデルを取得する

Embeddingの生成にはLLMとは別の専用モデルを使います。nomic-embed-textは274MBと軽量で、日本語・英語混在ドキュメントの埋め込みにも対応しています。

# Embedding専用モデルを取得 $ ollama pull nomic-embed-text pulling manifest... pulling 970aa74c0a90... 100% 274 MB success # LLM(質問応答用)も必要に応じて確認 $ ollama list NAME ID SIZE llama3.3:70b-instruct-q4_0 abc123... 42 GB nomic-embed-text:latest def456... 274 MB

2. MinIOをインストールしてsystemdサービスとして登録する

MinIOはシングルバイナリで動作するため、インストールはダウンロードとパーミッション設定だけで完結します。

# MinIOバイナリをダウンロード $ wget https://dl.min.io/server/minio/release/linux-amd64/minio -O /tmp/minio $ chmod +x /tmp/minio $ sudo mv /tmp/minio /usr/local/bin/minio # MinIOのデータディレクトリとシステムユーザーを作成 $ sudo useradd -r -s /sbin/nologin minio-user $ sudo mkdir -p /data/minio $ sudo chown minio-user:minio-user /data/minio # 環境変数ファイルを作成(パスワードは強固なものに変更する) $ sudo tee /etc/default/minio > /dev/null << 'EOF' MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=MinioPass2026! MINIO_VOLUMES=/data/minio MINIO_OPTS="--console-address :9001" EOF $ sudo chmod 600 /etc/default/minio

systemdサービスファイルを作成します。

$ sudo tee /etc/systemd/system/minio.service > /dev/null << 'EOF' [Unit] Description=MinIO Object Storage After=network-online.target Wants=network-online.target [Service] User=minio-user Group=minio-user EnvironmentFile=/etc/default/minio ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS Restart=always RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF $ sudo systemctl daemon-reload $ sudo systemctl enable --now minio $ systemctl status minio * minio.service - MinIO Object Storage Active: active (running)

MinIOにバケットとアクセスキーを設定する

MinIOが起動したら、Webコンソール(ポート9001)またはCLIでRAG用バケットを作成します。本番環境ではOllamaパイプライン専用のサービスアカウントを作成し、最小権限の原則に従ったアクセスキーを使うことを推奨します。

ここではCLI(mcコマンド)を使った操作手順を示します。

3. mcコマンドをインストールしてバケットを作成する

# MinIO Client(mc)をインストール $ wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /tmp/mc $ chmod +x /tmp/mc $ sudo mv /tmp/mc /usr/local/bin/mc # ローカルのMinIOをエイリアス登録 $ mc alias set local http://127.0.0.1:9000 minioadmin MinioPass2026! Added `local` successfully. # RAG用バケットを作成 $ mc mb local/rag-docs Bucket created successfully `local/rag-docs`. # バケット一覧確認 $ mc ls local [2026-10-12 10:00:00 JST] 0B rag-docs/

次に、Pythonパイプライン専用のサービスアカウントを作成します。Webコンソール(http://サーバーIP:9001)から「Access Keys」→「Create access key」で生成するか、mc adminコマンドで操作できます。生成したアクセスキーIDとシークレットキーは後の手順で使用します。

PythonでMinIOからドキュメントを取得してEmbeddingを生成する

ここからがパイプラインの核心部分です。Pythonスクリプトが次の処理を順に行います。MinIOからドキュメントを取得し、テキストを抽出し、Ollamaのnomic-embed-textでEmbeddingを生成し、Chromaに格納します。

まず必要なパッケージをインストールします。

4. 依存パッケージをインストールする

# Python仮想環境を作成 $ python3 -m venv ~/rag-env $ source ~/rag-env/bin/activate # 必要なパッケージをインストール $ pip install minio chromadb requests pypdf2 python-docx # インストール確認 $ pip list | grep -E "minio|chromadb|requests" chromadb 0.5.x minio 7.x.x requests 2.x.x

5. Embeddingパイプラインスクリプトを作成する

~/rag-env/index_documents.py として以下のスクリプトを作成します。MinIOからすべてのPDF・テキストファイルを取得し、OllamaのEmbeddingを生成してChromaに格納します。

#!/usr/bin/env python3 import io import requests from minio import Minio from PyPDF2 import PdfReader import chromadb MINIO_ENDPOINT = "127.0.0.1:9000" MINIO_ACCESS = "rag-service-access-key" MINIO_SECRET = "rag-service-secret-key" BUCKET = "rag-docs" OLLAMA_URL = "http://127.0.0.1:11434/api/embeddings" EMBED_MODEL = "nomic-embed-text" minio_client = Minio(MINIO_ENDPOINT, access_key=MINIO_ACCESS, secret_key=MINIO_SECRET, secure=False) chroma_client = chromadb.PersistentClient(path="/data/chroma_db") collection = chroma_client.get_or_create_collection("rag_docs") def extract_text(data: bytes, filename: str) -> str: if filename.endswith(".pdf"): reader = PdfReader(io.BytesIO(data)) return "\n".join(p.extract_text() or "" for p in reader.pages) return data.decode("utf-8", errors="ignore") def get_embedding(text: str) -> list: res = requests.post(OLLAMA_URL, json={"model": EMBED_MODEL, "prompt": text}) res.raise_for_status() return res.json()["embedding"] def index_all(): objects = minio_client.list_objects(BUCKET, recursive=True) for obj in objects: data = minio_client.get_object(BUCKET, obj.object_name).read() text = extract_text(data, obj.object_name) if not text.strip(): continue embedding = get_embedding(text[:2000]) collection.upsert( ids=[obj.object_name], embeddings=[embedding], documents=[text[:2000]], metadatas=[{"source": obj.object_name}] ) print(f"Indexed: {obj.object_name}") if __name__ == "__main__": index_all() print("Indexing complete.")

# テスト用文書をMinIOにアップロード $ echo "Linuxのsystemdでサービスを管理するには systemctl コマンドを使います。" \ > /tmp/test-doc.txt $ mc cp /tmp/test-doc.txt local/rag-docs/test-doc.txt # パイプラインを実行してインデックスを作成 $ source ~/rag-env/bin/activate $ python3 ~/rag-env/index_documents.py Indexed: test-doc.txt Indexing complete.

ChromaのインデックスをRAG質問応答に使う

Embeddingがチロmaに格納されたら、質問応答パイプラインを構築します。ユーザーの質問をEmbeddingに変換してChromaで類似検索を行い、関連ドキュメントをコンテキストとしてLLMへ渡す流れです。モデルの選択や量子化の判断については「ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント」が参考になります。

6. RAG質問応答スクリプトを作成する

~/rag-env/query_rag.py として以下のスクリプトを作成します。

#!/usr/bin/env python3 import sys import requests import chromadb OLLAMA_URL = "http://127.0.0.1:11434/api/embeddings" GENERATE_URL = "http://127.0.0.1:11434/api/generate" EMBED_MODEL = "nomic-embed-text" LLM_MODEL = "llama3.3:70b-instruct-q4_0" chroma_client = chromadb.PersistentClient(path="/data/chroma_db") collection = chroma_client.get_collection("rag_docs") def get_embedding(text: str) -> list: res = requests.post(OLLAMA_URL, json={"model": EMBED_MODEL, "prompt": text}) res.raise_for_status() return res.json()["embedding"] def query_rag(question: str, n_results: int = 3) -> str: q_embedding = get_embedding(question) results = collection.query( query_embeddings=[q_embedding], n_results=n_results ) context = "\n\n".join(results["documents"][0]) prompt = f"""以下のコンテキストを参照して質問に答えてください。 コンテキスト: {context} 質問: {question} 回答:""" res = requests.post(GENERATE_URL, json={"model": LLM_MODEL, "prompt": prompt, "stream": False}) res.raise_for_status() return res.json()["response"] if __name__ == "__main__": question = sys.argv[1] if len(sys.argv) > 1 else "systemdとは何ですか?" answer = query_rag(question) print(f"Q: {question}") print(f"A: {answer}")

# 質問応答を実行して動作確認 $ python3 ~/rag-env/query_rag.py "systemdでサービスを管理する方法は?" Q: systemdでサービスを管理する方法は? A: systemctlコマンドを使います。例えば、サービスの起動は systemctl start サービス名、停止はsystemctl stop サービス名です。

MinIOに格納したドキュメントの内容に基づいた回答が返れば、RAGパイプラインが正常に動作しています。

MinIOのイベント通知でインデックスを自動更新する

手動でインデックスを更新するのは運用として現実的ではありません。MinIOのイベント通知機能を使うと、バケットにファイルが追加されたタイミングで自動的にインデックス更新スクリプトを起動できます。

MinIOはWebhookやAMQP(RabbitMQ)・Kafkaなど複数の通知バックエンドをサポートしていますが、ここではシンプルなWebhookを使う方法を示します。

まず、ファイル追加イベントを受け取るWebhookサーバーを作成します。

7. Webhookサーバーとイベント設定を行う

# MinIOのWebhook通知先を設定(mcで設定) $ mc admin config set local \ notify_webhook:rag \ endpoint="http://127.0.0.1:8765/index" \ auth_token="" # 設定を反映するためMinIOを再起動 $ sudo systemctl restart minio # バケットのオブジェクト追加イベントをWebhookに紐付ける $ mc event add local/rag-docs arn:minio:sqs::rag:webhook \ --event put Successfully added arn:minio:sqs::rag:webhook

Webhookを受け取るFlaskサーバーを簡易実装します。

# flaskをインストール $ pip install flask # webhook_server.py を作成して起動 $ python3 ~/rag-env/webhook_server.py & # テスト:新しいドキュメントをアップロードしてイベントが発火するか確認 $ echo "Nginxはリバースプロキシとして広く使われるWebサーバーです。" \ > /tmp/nginx-doc.txt $ mc cp /tmp/nginx-doc.txt local/rag-docs/nginx-doc.txt /tmp/nginx-doc.txt: 40 B / 40 B [====] 100.00% # Webhookサーバーのログでインデックス更新が実行されたか確認 $ journalctl -f -u rag-webhook

よくあるトラブルと運用上の注意点

実際の運用でよく直面するトラブルとその対処法を整理します。

EmbeddingのAPIが503を返す場合、Ollamaがnomic-embed-textをメモリに保持していないことが原因です。ollama run nomic-embed-textで一度ロードするか、OLLAMA_KEEP_ALIVE環境変数で保持時間を延ばします(例: OLLAMA_KEEP_ALIVE=30m)。

ChromaのupsertでIDの衝突が発生する場合、同一ファイルを再アップロードしたときにIDが重複します。これはupsertの仕様上は上書きされるため問題ありませんが、ファイル内容が変わっていないのに毎回Embeddingを再生成するのは無駄です。MinIOのETagをIDに含めることで変更検知ができます。

RAGの回答品質が低い場合の原因は大きく2つです。まずドキュメントのチャンキング(分割単位)が適切でないケースです。2,000文字のまま格納するのでなく、500文字程度のチャンクに分割してオーバーラップを持たせる手法が有効です。次にEmbeddingモデルと質問言語の不一致です。日本語ドキュメントを多く扱う場合は、nomic-embed-textに加えてmxbai-embed-largeを比較することを検討してください。

MinIOのデータ耐久性については、本手順はシングルサーバー構成のためMinIO自体は冗長化されていません。本番環境では分散モード(4台以上のノード)か、Erasure Codingを有効にした構成を検討してください。また、/data/minioディレクトリの定期バックアップも必須です。

Embeddingの仕組みや類似検索の詳細については「Ubuntu ServerでローカルLLMを構築する方法」とあわせて確認してください。

本記事のまとめ

OllamaとMinIOを連携させた完全ローカルRAGシステムの構築手順を解説しました。各ステップと確認ポイントをまとめます。
手順 主なコマンド・処理 確認ポイント
Embeddingモデル取得 ollama pull nomic-embed-text ollama listに表示される
MinIOインストール・起動 systemctl status minio active (running)を確認
バケット作成 mc mb local/rag-docs mc ls localで一覧確認
インデックス作成 python3 index_documents.py Indexed: ファイル名が出力される
RAG質問応答確認 python3 query_rag.py "質問文" ドキュメント根拠の回答が返る
イベント自動更新 mc event add local/rag-docs ... ファイル追加時にWebhookが発火する
MinIOとOllamaの組み合わせは、既存のオブジェクトストレージ資産をそのままRAGの知識ベースとして活用できる点が強みです。S3互換APIのおかげで、ドキュメント管理の運用フローを変えずにAI検索機能を後付けできます。20年以上のサーバー運用経験から言うと、このように既存インフラへの変更を最小化しながら機能を追加できる構成は、実際の導入障壁が低く現場に定着しやすいです。

チャンキング戦略やマルチモーダル対応など発展的な構成に進む前に、まず今回のシンプルなパイプラインで動作を確認することを推奨します。

MinIO連携RAGをハンズオンで2日間で構築する

MinIOのセットアップからOllamaのEmbedding生成・RAGパイプライン構築まで、コードを書きながら学ぶ方向けに「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。

>> ローカルAIマスターセミナーの詳細を確認する
ローカルLLMの構築・運用に関する関連記事もあわせて参考にしてください。

・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。