そんな悩みを抱えるインフラエンジニア・情シス担当者は多いはずです。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を実現する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ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
$ 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/
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のイベント通知でインデックスを自動更新する
手動でインデックスを更新するのは運用として現実的ではありません。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
# 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連携RAGをハンズオンで2日間で構築する
MinIOのセットアップからOllamaのEmbedding生成・RAGパイプライン構築まで、コードを書きながら学ぶ方向けに「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。
・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:OllamaのモデルファイルをNFSで複数サーバーに共有する方法|共有ストレージでダウンロード1回・ディスク節約のクラスター構成手順
- この記事の属するカテゴリ:ローカルLLMへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます