メインコンテンツまでスキップ

Meilisearch セットアップ

Hetzner US(Ashburn)の VPS で Meilisearch を Docker セルフホストし、 Cloudflare Tunnel 経由で Cloudflare Workers (subseek-api) から到達する構成。

アーキテクチャ概要

Cloudflare Workers (subseek-api / subseek-api-staging)

│ HTTPS (Cloudflare 内部ネットワーク)

meili.subseek.cc / meili-staging.subseek.cc
(Cloudflare DNS + CNAME)

│ Cloudflare Tunnel (cloudflared)

Hetzner VM 上の cloudflared (systemd)

│ http://localhost:7700 / :7701

Meilisearch コンテナ (Docker, 127.0.0.1 バインド)

Why Cloudflare Tunnel か

Cloudflare Workers の fetch()HTTP(平文)+ 直 IP + 非標準ポート の宛先に対し、 Cloudflare のエッジネットワーク経由で Cloudflare 1003 ページ (error code: 1003 = "Direct IP access not allowed")を返してしまうケースがある。 Hetzner VM 上の Meilisearch を http://<HETZNER_VM_IP>:7700 で直接呼ぼうとすると Workers 側で SyntaxError: ... is not valid JSON が発生し検索が壊れる。

Cloudflare Tunnel を使うと:

  • 経路の大半が Cloudflare 内部ネットワーク → Workers から確実に到達
  • 自動 HTTPS 化(証明書管理不要)
  • VM 側の inbound ポートを完全に閉じられる(Tunnel は outbound のみで成立)

サーバー情報

項目
プロバイダーHetzner Cloud
プロジェクトsubseek
サーバー名meilisearch
ロケーションAshburn, VA(us-east)
プランCPX11(Shared, Regular Performance)
OSUbuntu 24.04
IPv4<HETZNER_VM_IP>

コンテナ構成

1台の VM 上で Production / Staging の2コンテナを起動する。いずれも 127.0.0.1 限定バインドで、 外部ネットワークからは直接到達不可。Cloudflare Tunnel 経由でのみ公開される。

環境コンテナ名バインドMEILI_ENV
Productionmeilisearch-production127.0.0.1:7700production
Stagingmeilisearch-staging127.0.0.1:7701development
Developmentローカル Docker0.0.0.0:7700development

初回セットアップ手順

Phase 1. SSH 接続

ssh root@`<HETZNER_VM_IP>`

Phase 2. Docker インストール

curl -fsSL https://get.docker.com | sh

Phase 3. Meilisearch 起動(localhost バインド)

mkdir -p /opt/meilisearch/data/{production,staging}

# マスターキー生成
PROD_KEY=$(openssl rand -hex 32)
STG_KEY=$(openssl rand -hex 32)
echo "Production MEILI_MASTER_KEY: $PROD_KEY"
echo "Staging MEILI_MASTER_KEY: $STG_KEY"

# Production(127.0.0.1:7700)
docker run -d \
--name meilisearch-production \
--restart unless-stopped \
-p 127.0.0.1:7700:7700 \
-v /opt/meilisearch/data/production:/meili_data \
-e MEILI_MASTER_KEY="$PROD_KEY" \
-e MEILI_ENV=production \
getmeili/meilisearch:v1.13

# Staging(127.0.0.1:7701)
docker run -d \
--name meilisearch-staging \
--restart unless-stopped \
-p 127.0.0.1:7701:7700 \
-v /opt/meilisearch/data/staging:/meili_data \
-e MEILI_MASTER_KEY="$STG_KEY" \
-e MEILI_ENV=development \
getmeili/meilisearch:v1.13

重要: MEILI_MASTER_KEY の値は安全な場所に保管すること。docker inspect で後から確認可能だが、紛失すると API キーの再取得ができなくなる。

Why 127.0.0.1: プレフィックスが必須か: Docker は -p 7700:7700 のように IP を省略すると iptables を直接操作して ufw のルールを bypass し、外部からアクセス可能になる。Tunnel のみで公開する 構成では必ず -p 127.0.0.1:7700:7700 の形でバインドし、ホスト側ループバックに 限定する必要がある。

Phase 4. ローカル動作確認

curl localhost:7700/health   # → {"status":"available"}
curl localhost:7701/health # → {"status":"available"}

Phase 5. Cloudflare Tunnel セットアップ

Phase 5.1. cloudflared インストール

curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
. /etc/os-release
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $UBUNTU_CODENAME main" | tee /etc/apt/sources.list.d/cloudflared.list
apt-get update
apt-get install -y cloudflared

Phase 5.2. Cloudflare アカウントへのログイン

cloudflared tunnel login

表示される URL をブラウザで開き、subseek.cc ゾーンを選択して承認。 認証成功で /root/.cloudflared/cert.pem が自動配置される。

Phase 5.3. Tunnel 作成

cloudflared tunnel create meilisearch
# → Tunnel UUID が出力される。/root/.cloudflared/<UUID>.json も生成

UUID は次の Phase 5.4 の config.yml に書く。

Phase 5.4. config.yml 作成

TUNNEL_ID=<前 Phase で出た UUID>
mkdir -p /etc/cloudflared
cat > /etc/cloudflared/config.yml <<EOF
tunnel: $TUNNEL_ID
credentials-file: /root/.cloudflared/$TUNNEL_ID.json

ingress:
- hostname: meili.subseek.cc
service: http://localhost:7700
- hostname: meili-staging.subseek.cc
service: http://localhost:7701
- service: http_status:404
EOF

Phase 5.5. DNS ルーティング

cloudflared tunnel route dns meilisearch meili.subseek.cc
cloudflared tunnel route dns meilisearch meili-staging.subseek.cc

subseek.cc ゾーンに CNAME が自動追加される。

Phase 5.6. systemd サービス常駐

cloudflared service install
systemctl status cloudflared

Phase 5.7. 疎通確認(ローカル PC から)

curl https://meili.subseek.cc/health           # → {"status":"available"}
curl https://meili-staging.subseek.cc/health # → {"status":"available"}

Phase 6. ファイアウォール設定

Tunnel 経由でのみ外部公開する構成のため、SSH 以外の inbound はすべて拒否する。

ufw allow 22/tcp
ufw default deny incoming
ufw default allow outgoing
ufw enable
ufw status

Why Meilisearch ポートを ufw で開けないか: コンテナを 127.0.0.1:7700 バインドにしているため、外部からの直接到達は Linux カーネルレベルで不可。ufw で 7700/7701 を開けても無意味。

Phase 7. API キーの取得

Meilisearch は起動時にマスターキーから Default Search API Key と Default Admin API Key を自動生成する。

# Production
curl -s http://localhost:7700/keys \
-H "Authorization: Bearer <PROD_MEILI_MASTER_KEY>" \
| jq '.results[] | {name, key}'

# Staging
curl -s http://localhost:7701/keys \
-H "Authorization: Bearer <STG_MEILI_MASTER_KEY>" \
| jq '.results[] | {name, key}'

Admin API Key を Cloudflare Workers の MEILISEARCH_API_KEY シークレットに設定する。

Phase 8. Cloudflare Workers シークレット設定

ローカル開発機から wrangler で設定する。値は標準入力で渡す (echo だと末尾改行が混入するため printf を使う)。

cd apps/api

# Production
printf 'https://meili.subseek.cc' | pnpm exec wrangler secret put MEILISEARCH_HOST
printf '<Production Admin API Key>' | pnpm exec wrangler secret put MEILISEARCH_API_KEY

# Staging
printf 'https://meili-staging.subseek.cc' | pnpm exec wrangler secret put MEILISEARCH_HOST --env staging
printf '<Staging Admin API Key>' | pnpm exec wrangler secret put MEILISEARCH_API_KEY --env staging

シークレット変更で Worker は自動再デプロイされる。

GitHub Actions(pnpm meilisearch:init を CI から実行する用)の Repository Secrets も同様の値で揃える:

gh secret set PROD_MEILISEARCH_HOST --body "https://meili.subseek.cc" -R nito-tech/subseek
gh secret set PROD_MEILISEARCH_API_KEY --body "<Production Admin API Key>" -R nito-tech/subseek
gh secret set STAGING_MEILISEARCH_HOST --body "https://meili-staging.subseek.cc" -R nito-tech/subseek
gh secret set STAGING_MEILISEARCH_API_KEY --body "<Staging Admin API Key>" -R nito-tech/subseek

運用コマンド

コンテナの状態確認

ssh root@`<HETZNER_VM_IP>` docker ps

Meilisearch ログ確認

# Production
ssh root@`<HETZNER_VM_IP>` docker logs meilisearch-production --tail 50

# Staging
ssh root@`<HETZNER_VM_IP>` docker logs meilisearch-staging --tail 50

Cloudflare Tunnel ログ確認

ssh root@`<HETZNER_VM_IP>` journalctl -u cloudflared -n 100 --no-pager

Meilisearch バージョンアップ

Staging で先に検証してから Production に適用する。

ssh root@`<HETZNER_VM_IP>` bash <<'REMOTE'
# Staging を先にアップグレード
docker stop meilisearch-staging
docker rm meilisearch-staging
docker pull getmeili/meilisearch:v1.14 # 新バージョン

docker run -d \
--name meilisearch-staging \
--restart unless-stopped \
-p 127.0.0.1:7701:7700 \
-v /opt/meilisearch/data/staging:/meili_data \
-e MEILI_MASTER_KEY="<STG_MEILI_MASTER_KEY>" \
-e MEILI_ENV=development \
getmeili/meilisearch:v1.14

# 動作確認後、Production も同様に実施
REMOTE

マスターキーの確認

マスターキーを忘れた場合、コンテナの環境変数から確認できる。

docker inspect meilisearch-production --format '{{range .Config.Env}}{{println .}}{{end}}' | grep MEILI_MASTER_KEY
docker inspect meilisearch-staging --format '{{range .Config.Env}}{{println .}}{{end}}' | grep MEILI_MASTER_KEY

Tunnel の再起動 / 設定変更

config.yml を編集した後は cloudflared サービスを再起動する。

ssh root@`<HETZNER_VM_IP>` 'systemctl restart cloudflared && systemctl status cloudflared'

ローカル開発

ローカルではリポジトリルートの docker-compose.yml で Meilisearch を起動する。 本番 (Hetzner) と同じ getmeili/meilisearch:v1.13 を使用し、MEILI_ENV=development で立ち上げる。

なお、pnpm dev を実行すると scripts/check-prerequisites.sh が自動で Meilisearch の起動状態を確認し、未起動であればバックグラウンドで立ち上げるため、通常は明示的な起動コマンドは不要。

起動・停止

# バックグラウンド起動
docker compose up -d meilisearch

# ログ確認
docker compose logs -f meilisearch

# ヘルスチェック
curl http://localhost:7700/health # → {"status":"available"}

# 停止(データは維持)
docker compose down

# 停止してボリュームも削除(インデックス初期化)
docker compose down -v

環境変数

ローカル開発時の apps/api/.env には以下を設定する(apps/api/.env.example 参照)。

変数
MEILISEARCH_HOSThttp://localhost:7700
MEILISEARCH_API_KEYlocal-dev-meilisearch-keydocker-compose.ymlMEILI_MASTER_KEY デフォルト値と一致)

インデックスのドキュメント構造について

subtitles インデックスのドキュメントフィールド一覧・searchable / filterable / sortable 属性・冗長保存フィールドの同期戦略などスキーマ面の設計は Meilisearch インデックススキーマ に分離した。更新タイミングの詳細は Meilisearch 更新タイミング を参照。

本ページは VM / Docker / Tunnel / API キーといった 運用まわり に集中させる方針。