上級 22 分Docker

Dockerを使ってLLMを本番環境にデプロイする Compose

自分のPCでLLMをローカル実行するなら、10分でできます。一方、Docker Composeを使い、HTTPSのURL、モニタリング、バックアップ、そしてしっかりしたセキュリティを備えたチーム向けの本番環境にLLMをデプロイするのは、別の作業です。このガイドでは、完全なスタック(Ollama、Open WebUI、Qdrant、n8n、Traefik)を、コメント付きの単一の docker-compose.yml にまとめ、VPSやオンプレミスサーバーにそのまま配置できるようにしています。

著者 Mohamed Meguedmi·更新 2026-08-27·Windows・macOS・Linuxでテスト済み

#このスタックを選ぶ理由

目標は、ローカルAIを導入する中小企業や技術チームの4つの具体的なニーズを満たすことです。推論エンジン(Ollama)、Webユーザーインターフェース(Open WebUI)、RAG用のベクトルデータベース(Qdrant)、そしてノーコードの自動化レイヤー(n8n)です。これらの前段にリバースプロキシとしてTraefikを配置し、TraefikがLet's Encryptを使ったHTTPSとルーティングを担当します。

各サービスは、永続化ボリュームを備えた独立したコンテナ内で動作します。他のサービスを壊すことなく1つを無効にしたり、単一のコンポーネントのみを更新したり、1つのコマンドでスタックをステージング環境に複製したりできます。

i
このガイドに含まれない内容
これはKubernetesのガイドではありません。10〜50人のチームが単一サーバー(1つのGPUで最大約10人の同時ユーザー)を使用する場合、Docker Composeで十分です。それ以上、またはマルチノードのハイアベイラビリティが必要な場合は、k3sやNomadを検討してください。

#ターゲットアーキテクチャ

ローカルエージェントキット

あなたのマシンで動作するエージェント:エージェント型 Cline、MCP、n8n + Ollama、ローカル自動化。

  • 永久に利用できるオンラインスペース
  • PDF + ファイル
  • 生涯アップデート
Traefik (ポート 80/443)
リバースプロキシと、Let's Encryptを利用した自動TLS終端を担います。すべての外部トラフィックはこれを経由します。
Open WebUI(内部)
ChatGPT風のチャットインターフェースで、chat.votre-domaine.fr上で提供されます。認証はローカル認証またはOIDCです。
Ollama (内部)
推論デーモン。公開されません。Dockerネットワーク内からのみアクセス可能です。
Qdrant(内部)
RAG用のベクトルデータベース。ドキュメントの埋め込みベクトルを保存します。
n8n
ノーコード自動化。n8n.votre-domaine.frからアクセスできます。
Prometheus + Grafana
オプションであり、GPU/CPU/RAMのモニタリング用に内部で公開されています。

#前提条件

Linuxサーバー
Ubuntu 22.04 LTSまたはDebian 12、root権限でのSSHアクセス、最低16GBのRAM、200GBのディスク容量。
NVIDIA製GPUを推奨
14B~32B のモデルを快適に動かすなら RTX 3090 24GB または RTX 4090、7B のモデルなら RTX 4070 12GB が適しています。GPU がない場合は、CPU で動かすモデルを 3B にとどめてください。
ドメイン名
DNSにアクセスしてサブドメインを作成できる。Let's Encrypt に必須
Docker Engine + Compose v2
公式スクリプトget.docker.comでインストールします。2022年以降、composeプラグインが含まれています。
NVIDIA Container Toolkit
GPUをお持ちの場合、これはDockerがコンテナにそのGPUを公開できるようにするものです。

#1. サーバーの準備

クリーンなUbuntu 22.04から始めます。インストールが必要なものは3つです:Docker、NVIDIA Container Toolkit(GPUがある場合)、そして適切なファイアウォール。

Docker インストール
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
docker compose version
NVIDIA Container Toolkit
distribution=$(. /etc/os-release; echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
DockerがGPUを認識しているかテストする
docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi
UFWの最小限のファイアウォール
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP - redir vers HTTPS'
sudo ufw allow 443/tcp comment 'HTTPS Traefik'
sudo ufw enable
!
Ollamaを直接公開することは絶対に避けてください
Ollama のポート 11434 には認証機能がありません。インターネットに公開すると、誰でも GPU リソースを使い、API 経由でモデルを外部に持ち出せるようになります。すべての通信は、認証を備えた Traefik を経由します。

#2. docker-compose.yml(コメント付き)

作業フォルダとメインファイルを作成してください。これがデプロイの中核となります。全体の動作を壊さずに調整できるよう、各セクションにコメントが付いています。

ディレクトリ構成
mkdir -p /opt/llm-stack/{traefik,ollama,open-webui,qdrant,n8n}
cd /opt/llm-stack
touch docker-compose.yml .env
.env(要記入)
DOMAIN=votre-domaine.fr
ACME_EMAIL=admin@votre-domaine.fr
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=changez-moi-vraiment
TRAEFIK_DASHBOARD_AUTH=admin:$$apr1$$xxxxxxx  # generé avec htpasswd
docker-compose.yml
name: llm-stack

networks:
  proxy:      # réseau public exposé par Traefik
  internal:   # réseau privé Ollama <-> WebUI <-> Qdrant

volumes:
  ollama_data:
  open_webui_data:
  qdrant_data:
  n8n_data:
  traefik_certs:

services:

  # --- Traefik : reverse proxy + Let's Encrypt automatique ---
  traefik:
    image: traefik:v3.2
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/certs/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
      - --api.dashboard=true
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_certs:/certs
    networks: [proxy]
    labels:
      - traefik.enable=true
      - traefik.http.routers.dashboard.rule=Host(`traefik.${DOMAIN}`)
      - traefik.http.routers.dashboard.service=api@internal
      - traefik.http.routers.dashboard.entrypoints=websecure
      - traefik.http.routers.dashboard.tls.certresolver=le
      - traefik.http.routers.dashboard.middlewares=dashboard-auth
      - traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}

  # --- Ollama : moteur d'inférence, JAMAIS exposé en public ---
  ollama:
    image: ollama/ollama:latest
    restart: unless-stopped
    volumes:
      - ollama_data:/root/.ollama
    environment:
      - OLLAMA_HOST=0.0.0.0:11434
      - OLLAMA_KEEP_ALIVE=24h
      - OLLAMA_NUM_PARALLEL=2
    networks: [internal]
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

  # --- Open WebUI : interface chat, exposée en HTTPS ---
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    restart: unless-stopped
    depends_on: [ollama]
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
      - WEBUI_AUTH=true
      - ENABLE_SIGNUP=false
    volumes:
      - open_webui_data:/app/backend/data
    networks: [proxy, internal]
    labels:
      - traefik.enable=true
      - traefik.http.routers.chat.rule=Host(`chat.${DOMAIN}`)
      - traefik.http.routers.chat.entrypoints=websecure
      - traefik.http.routers.chat.tls.certresolver=le
      - traefik.http.services.chat.loadbalancer.server.port=8080

  # --- Qdrant : base vectorielle pour le RAG ---
  qdrant:
    image: qdrant/qdrant:latest
    restart: unless-stopped
    volumes:
      - qdrant_data:/qdrant/storage
    networks: [internal]

  # --- n8n : automatisation no-code ---
  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
      - N8N_HOST=n8n.${DOMAIN}
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.${DOMAIN}/
    volumes:
      - n8n_data:/home/node/.n8n
    networks: [proxy, internal]
    labels:
      - traefik.enable=true
      - traefik.http.routers.n8n.rule=Host(`n8n.${DOMAIN}`)
      - traefik.http.routers.n8n.entrypoints=websecure
      - traefik.http.routers.n8n.tls.certresolver=le
      - traefik.http.services.n8n.loadbalancer.server.port=5678
→
なぜ2つのネットワークなのか
internalネットワークからはリバースプロキシにアクセスできません。OllamaとQdrantはこのネットワーク内に隔離されているため、Traefikを経由した攻撃者が両者のAPIに直接アクセスする手段はありません。Open WebUIは両方のネットワークに接続されており、proxy経由で公開トラフィックを受信し、internal経由でOllamaと通信します。

#3. Traefik および DNS の設定

最初の docker compose up を実行する前に、サーバーの公開 IP アドレスを指す A レコードの DNS エントリを 3 つ作成してください。

chat.votre-domaine.fr
ユーザーインターフェースのOpen WebUI。
n8n.votre-domaine.fr
ワークフロー編集ツール n8n。
traefik.votre-domaine.fr
Traefikのダッシュボード(basic authで保護されています)

Traefik ダッシュボードの basic auth 用のハッシュを生成します。注意:.env 内では $ を二重にしてください(docker-compose のエスケープ)。

Basic認証用のハッシュ
sudo apt install apache2-utils
htpasswd -nb admin VotreMotDePasse | sed -e 's/\$/\$\$/g'
!
Let's Encryptおよびレート制限
Let's Encrypt はドメインごと、週ごとに50件の証明書までを制限しています。テストフェーズでは --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory を使用し、ステージングサーバーに指向してください。すべての機能が正常に動作するまでその設定を変更してください。

#4. 初回起動および確認

  1. 01
    スタックを起動する
    /opt/llm-stack内でdocker compose up -dを実行してください。Traefikが数秒でLet's Encryptの証明書を取得するので、ログを監視してください。
  2. 02
    サービスの稼働状態を確認する
    docker compose ps doit afficher tous les services en running. Aucun ne doit redémarrer en boucle.
  3. 03
    最初のモデルをダウンロードする
    docker compose exec ollama ollama pull qwen3.5:9b(6.6 GB、コンテキスト256k、2026年の標準的な8 GB向け汎用モデル)。24 GB GPUでより高性能なモデルを使う場合:qwen3.8:27b(VRAM約18 GB、コンテキスト262k、Apache 2.0ライセンス)。
  4. 04
    インターフェースをテスト
    https://chat.votre-domaine.fr を開いてください。最初に作成されたアカウントが管理者になります。ENABLE_SIGNUP=falseにすると、それ以降、一般ユーザーによる新規登録がすべてブロックされます。
  5. 05
    GPUが実際に使用されているかを確認
    docker compose exec ollama ollama ps : la colonne PROCESSOR doit afficher 100% GPU.
Traefikのログをリアルタイムで表示
docker compose logs -f traefik | grep -i acme

#5. セキュリティの強化

設定が不適切なパブリックスタックは、1時間以内にスキャンされます。Docker ComposeによるLLMの本番環境デプロイメントが、誰かのshodan.ioに載らないための、妥協の許されない6つのルールをご紹介します。

強力で使い回さないパスワード
openssl rand -base64 32 でシークレットを生成してください。.envファイルに記載されている例のパスワードは一切使用しないでください。
一般ユーザーの新規登録を無効にする
Open WebUIでENABLE_SIGNUP=falseを設定してください。これを設定しないと、誰でもアカウントを作成し、あなたのGPUリソースを使用できます。
Traefikミドルウェアを介したセキュリティヘッダー
HSTS、frame-deny、content-type-nosniffを備えたsecureHeadersミドルウェアを追加してください。わずか3行で、大きな効果が得られます。
コンテナごとのリソース制限
deploy.resources.limitsの下に、memoryとcpusを設定してください。サービスがクラッシュした場合、サーバー全体をダウンさせないようにします。
可能な限り読み取り専用のコンテナを使用
TraefikおよびQdrantにread_only: trueを追加してください。一時的な書き込みディレクトリにはTmpfsを使用してください。
月次更新
watchtower は自動プルを実行しますが、プロダクション環境でのバージョン管理には、cron タスクと明示的な git tag の組み合わせを推奨します。
セキュリティヘッダー用ミドルウェア
# À ajouter sur le service open-webui en labels:
- traefik.http.routers.chat.middlewares=secure-headers
- traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000
- traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true
- traefik.http.middlewares.secure-headers.headers.frameDeny=true
- traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true
- traefik.http.middlewares.secure-headers.headers.browserXssFilter=true
→
Traefikの前段にFail2banを配置
Basic認証(Traefikのダッシュボード、n8n)への総当たり攻撃を防ぐには、Traefikのログを解析するフィルターとともにfail2banをインストールしてください。5回の試行 → IPアドレスを1時間ブロック。設定は10行で完了します。

#6. Prometheus+Grafanaによる監視

本番環境で必ず監視すべき3つの指標:Ollama によるVRAM使用量、Open WebUI のリクエスト遅延、およびLet's Encrypt証明書の状態(Traefikはすべてを /metrics で公開します)。

docker-compose.yml に追加するブロック
  prometheus:
    image: prom/prometheus:latest
    restart: unless-stopped
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    networks: [internal]

  nvidia-exporter:
    image: utkuozdemir/nvidia_gpu_exporter:latest
    restart: unless-stopped
    devices: [/dev/nvidiactl, /dev/nvidia0]
    volumes:
      - /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1:/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1:ro
      - /usr/bin/nvidia-smi:/usr/bin/nvidia-smi:ro
    networks: [internal]

  grafana:
    image: grafana/grafana:latest
    restart: unless-stopped
    volumes:
      - grafana_data:/var/lib/grafana
    networks: [proxy, internal]
    labels:
      - traefik.enable=true
      - traefik.http.routers.grafana.rule=Host(`grafana.${DOMAIN}`)
      - traefik.http.routers.grafana.entrypoints=websecure
      - traefik.http.routers.grafana.tls.certresolver=le
      - traefik.http.services.grafana.loadbalancer.server.port=3000
prometheus.yml
global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'traefik'
    static_configs:
      - targets: ['traefik:8080']
  - job_name: 'nvidia'
    static_configs:
      - targets: ['nvidia-exporter:9835']
  - job_name: 'qdrant'
    static_configs:
      - targets: ['qdrant:6333']

Grafana では、ダッシュボード ID 14574(NVIDIA GPU Exporter)と17346(Traefik v3)をインポートしてください。5分で、VRAM、GPU 温度、サブドメイン別の HTTP リクエストレート、P95 レイテンシをまとめて把握できます。

#7. バックアップと更新

5つのボリュームにすべての重要データが含まれます:ollama_data(モデル)、open_webui_data(アカウント + 会話 + RAG)、qdrant_data(ベクトル)、n8n_data(ワークフロー)、traefik_certs(証明書)。

/usr/local/bin/backup-llm-stack.sh
#!/bin/bash
set -e
BACKUP_DIR=/var/backups/llm-stack
TS=$(date +%Y%m%d-%H%M)
mkdir -p "$BACKUP_DIR"

for vol in open_webui_data qdrant_data n8n_data traefik_certs; do
  docker run --rm \
    -v llm-stack_${vol}:/data \
    -v "$BACKUP_DIR":/backup \
    alpine tar czf "/backup/${vol}-${TS}.tar.gz" -C /data .
done

find "$BACKUP_DIR" -name '*.tar.gz' -mtime +30 -delete
毎日午前3時に実行するcron設定
sudo chmod +x /usr/local/bin/backup-llm-stack.sh
echo "0 3 * * * root /usr/local/bin/backup-llm-stack.sh" | sudo tee /etc/cron.d/llm-stack-backup
i
ollama_data をバックアップしない理由
モデルはすぐに数十GBに達し、ollama pullで再ダウンロードできます。日々のバックアップにモデルを含める必要はありません。代わりに、モデルのリストを保存してください:docker compose exec ollama ollama list > models.txt。

アップデートは慎重に行ってください。バージョンを固定し(image: ollama/ollama:0.5.4を使い、:latestは使わない)、更新のたびに事前にVPSのスナップショットを取り、まずステージング環境でテストしてください。

適切な更新手順
cd /opt/llm-stack
docker compose pull
docker compose up -d
docker image prune -f

#トラブルシューティング

Traefik は証明書を生成しません
ポート80と443が開いていること、そしてDNSが正しくサーバーを指していることの両方を確認してください。docker compose logs traefik | grep -i error で、問題の90%を特定できます(DNSがまだ伝播していない、レート制限、チャレンジの失敗)。
Open WebUIに 'No models found' と表示される
Ollama でまだモデルをダウンロードしていません。docker compose exec ollama ollama pull qwen3.5:9b を実行してください。また、OLLAMA_BASE_URL=http://ollama:11434 も確認してください(localhost ではなく、サービス名を指定します)。
GPUがコンテナ内で検出されません
nvidia-container-toolkit がインストールされていないか、Docker が設定後に再起動されていません。docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi でテストしてください。
n8nがHTTPへのリダイレクトを繰り返します
WEBHOOK_URL と N8N_PROTOCOL=https を設定する必要があります。設定しないと、n8nはHTTPで動作していると認識し、ブラウザの動作がおかしくなります。
Traefikの後ろにあるGrafanaにアクセスできません
そのenvironmentにGF_SERVER_ROOT_URL=https://grafana.${DOMAIN}とGF_SERVER_SERVE_FROM_SUB_PATH=falseを追加してください。
複数ユーザーが利用する場合、遅延が急激に増加します
OLLAMA_NUM_PARALLELの設定値がVRAM容量に対して高すぎます。グラフィックカードに応じて1または2に下げてください。負荷がかかっている間はollama psで状態を確認してください。

#さらに詳しく

スタックは稼働し、監視とバックアップも行われています。運用をさらに本格化するための次のステップとして、3つの方向性が考えられます。

RAGをあなたのドキュメントに接続する
ChromaDBとMistralを使うRAGのガイドは、Qdrantにも容易に応用できます。埋め込みとチャンク分割の考え方は同じで、使うベクトルデータベースが異なるだけです。
ビジネスプロセスの自動化
「n8nとOllamaで自動化する」ガイドでは、メールの翻訳、リードの分類、RSSの要約など、具体的な10のレシピが紹介されており、これらはあなたのスタックを直接活用します。
コンプライアンスに対応する
「ローカルLLMとRGPD」ガイドでは、企業での導入にあたり文書化すべき法的義務(記録簿、保存期間、ユーザーの権利)を詳しく説明しています。
このガイドは役に立ちましたか?

ご意見、誤りのご指摘、補足はありますか?ぜひお知らせください。皆さんにとってより良いガイドにするために役立ちます。