株式会社ホコサキ

histerで作る軽量セルフホスト全文検索環境

天京祐輔
天京祐輔
histerで作る軽量セルフホスト全文検索環境

社内 Wiki やヘルプページが増えてくると、「あの情報どこだっけ」という検索体験がじわじわ悪化してきます。
Ctrl+F でページを開いて探すのはそもそも「どのページか」が分かってからの話で、grep はエンジニア以外には敷居が高い。

かといって Elasticsearch を立てるには、JVM のチューニングやクラスタ管理・定期的なバージョンアップが伴います。
「社内 Wiki を検索したいだけ」という用途には明らかにオーバースペックです。
Meilisearch も高速で優れたツールですが、インデックス設定の概念を把握するまでの慣れが必要で、小規模な用途には少し大げさな印象があります。

そこで選択肢として紹介したいのが hister です。
Go 製の単一バイナリで動く全文検索エンジンで、ブラウザ拡張機能が閲覧ページのコンテンツをサーバーに送ってインデックスする仕組みです。
設定ファイル1枚・ブラウザ UI 付きで、Docker Compose を使えば30分以内に検索環境を立ち上げることができます。

grep でも Ctrl+F でもない、ちょうどいい全文検索の選択肢

hister は Go で書かれた OSS の全文検索エンジンで、GitHub の asciimoo/hister リポジトリで公開されています。
公式のコンテナイメージ(ghcr.io/asciimoo/hister:latest)が配布されており、ビルド環境を用意せずに動かせます。

hister の動き方は、サーバーが URL を巡回する従来型のクローラーとは少し異なります。
ブラウザ拡張機能 が閲覧ページのコンテンツを hister サーバーに送り、サーバー側でインデックスを積み上げていく仕組みです。
「ブラウザで見たページが自動的に検索対象になっていく」というイメージが近いです。
既存のブラウザ履歴をまとめてインポートする機能もあるため、「これまで訪れた Wiki ページをまとめて検索対象にしたい」というケースにも素早く対応できます。

インデックスが積み上がると、サーバーは 127.0.0.1:4433 でブラウザ UI を提供し、キーワード検索できる状態になります。
ターミナルや MCP 経由(AI アシスタント)からも検索できる設計になっており、用途の広がりがあります。

配布バイナリを使う場合、ランタイムの依存関係はありません。
ソースからビルドする場合は Go・npm・C コンパイラの CGO 依存が必要ですが、公式イメージを使えばそこは気にしなくていいです。
デプロイが単純で、試してダメなら設定ごと消せる。
Elasticsearch や Meilisearch を本番環境に持っていくなら、まずそれらのセットアップと基本操作を覚えるコストが先に発生します。
hister はその学習コストをスキップして「とにかく動かして判断する」フェーズに直接入れるのが強みです。

プライバシー面も明確で、デフォルトではテレメトリなし・クラウド同期なしで動きます。
ブラウザ拡張機能が送信するコンテンツは、設定した hister サーバーにのみ届きます。
オプションとしてセマンティック検索も有効にできますが、外部の埋め込みエンドポイントを使う構成になるため、利用前にプライバシー設定を確認するのが無難です。

Elasticsearch・Meilisearch と何が違うか——選ぶ判断軸

hister をどういう判断軸で選ぶかを整理しておきます。
スペックを並べるより「どこで分岐するか」を考えた方が実用的なので、その観点で話します。

Elasticsearch は大規模・高可用性・複雑なクエリに強い一方、JVM を必要とし、適切なリソース設定が欠かせません。
ログ分析や EC サイトの商品検索のような本格的な用途には向いていますが、「社内 Wiki を横断検索したいだけ」なら過剰です。

Meilisearch は Rust 製で軽量・高速、日本語を含む100以上の言語を自動検出してインデックスできます。
セルフホストであれば MIT ライセンスで無料ですし、開発者体験も良好です。
API 設計やインデックス設定の概念を一通り把握するまでに多少の慣れが必要で、「とりあえず動かす」よりも「ちゃんと設定して使う」ツールという印象があります。
もっとも、それは使いこなせばそれだけ強力ということでもあります。

hister はこの2つとは別の層にいます。
「まず動かして判断したい」という入口の低さが最大の特徴です。

hister が向いているケース:

  • 社内 Wiki・ヘルプページなど、チームメンバーが日常的にアクセスするドキュメントの横断検索
  • 1人〜小チームでの運用で、管理コストをできるだけ下げたい
  • 全文検索の仕組みをざっくり体感してから本格ツールを選定したいフェーズ

向いていないケース:

  • ブラウザで訪れていないページも含め、サーバー側から網羅的にクロールしたい
  • 日本語ドキュメントを大量に扱い、形態素解析ベースの高精度検索が必要なケース(hister の日本語対応の詳細は別途検証を推奨)
  • 共有サーバーでユーザーごとにきめ細かいアクセス制御が必要(hister はマルチユーザーをサポートしていますが、複雑な権限制御が求められる場合は専用ツールを検討する価値があります)

「合わなければ捨てやすい」というのが軽量ツールの本質的な価値です。
Elasticsearch を本番に持っていってから「やっぱり合わなかった」となったときの撤退コストと、hister で30分試してから判断する場合のコストは、まるで違います。
小さく試して判断する、そのための道具として見るのが正しい使い方だと思っています。

Docker Compose で起動してインデックスを作る

前提は Docker と Docker Compose が使える環境のみです。
Go のビルド環境は不要で、公式コンテナイメージをそのまま使います。

まず設定ファイルを生成します。
以下のコマンドを実行すると、デフォルト設定が記述された config.yml がカレントディレクトリに作成されます。

docker run --rm ghcr.io/asciimoo/hister:latest config create > config.yml

生成された config.yml を開くと、ブラックリストやホットキー・センシティブデータのパターンなど、挙動をカスタマイズする設定項目が並んでいます。
インデックスしたくないパスや URL パターンはブラックリスト設定で除外できます。
具体的なフィールド名や構文は、生成した config.yml の中身と 公式ドキュメント を照合しながら確認してください。

次に compose.yml を用意します。
公式ドキュメントの基本構成に基づいた内容はこうなります。

services:
  hister:
    image: ghcr.io/asciimoo/hister:latest
    container_name: hister
    restart: unless-stopped
    volumes:
      - hister_data:/hister/data
      - ./config.yml:/hister/config.yml
    ports:
      - 4433:4433

volumes:
  hister_data:

named volume(hister_data)でインデックスデータをコンテナの外に永続化しています。
先ほど生成した config.yml も bind mount でコンテナ内に渡します。
この2点が後述する「データ保全」の基礎になります。

起動は1コマンドです。

docker compose up -d

コンテナが起動したら、次はブラウザ拡張機能のセットアップです。
拡張機能は hister.org のドキュメント に案内があるので、そこからインストールします。
インストール後、拡張機能の設定でサーバー URL として 127.0.0.1:4433 を指定します。
あとは社内 Wiki のページを実際に閲覧していくと、訪れたページのコンテンツが順次インデックスされていきます。

インデックスが積み上がったところでブラウザから 127.0.0.1:4433 にアクセスすると、検索フォームが表示されます。
キーワードを入れてドキュメントがヒットすれば、セットアップ完了です。

本番に持っていく前に考えること

ローカルで動作確認できたら、次は「チームに使ってもらう」フェーズです。
大げさに作り込む必要はありませんが、最低限の保護と運用の仕組みだけは入れておくと安心です。

リバースプロキシと認証

社内 LAN 限定でしか使わないなら、Basic 認証で十分です。
Caddy はリバースプロキシと HTTPS・Basic 認証を非常に簡単に設定できるので、hister と組み合わせやすいです。

以下は Caddy で hister をリバースプロキシしつつ Basic 認証を掛ける Caddyfile の例です。

search.example.internal {
    basicauth {
        alice $2a$14$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    }
    reverse_proxy hister:4433
}

basicauth ブロックの書き方は「ユーザー名 ハッシュ化パスワード」をスペース区切りで並べる形式です。
パスワードハッシュは Caddy が提供する hash-password コマンドで生成します。
平文をそのまま書く必要はありません。

インターネットに公開する場合は Basic 認証に加えて TLS を必ず設定してください。
社内ドキュメントを外部に公開する必要がないなら、最初から社内 LAN に閉じておくのがいちばんリスクが低いです。

インデックスの永続化と運用サイクル

コンテナを削除・再作成してもインデックスデータが消えないのは、compose.yml で named volume を使っているためです。
hister_data という volume はコンテナのライフサイクルと独立しているので、コンテナを作り直してもデータは残ります。
「docker compose down してデプロイし直したらデータが消えた」という事態は、この設定であれば防げます。

インデックスの更新については、チームメンバーが日常業務で Wiki を閲覧するたびに拡張機能経由で積み上がっていくのが hister の基本的なサイクルです。
バルクインポートや定期的なインデックス再構築など、より踏み込んだ操作については 公式ドキュメントの Server Setup とコマンドリファレンスを確認するのが確実です。

完璧に作り込んでから使い始めるより、最小構成で動かして実際の使い勝手を見た方が判断が早いです。
認証を仕込んでチームに展開し、あとは使いながら育てる感覚で進めやすいと思います。

30分で動かせた先に見えるもの

仕様書やドキュメントを読んでも分からないことがあります。
インデックスの精度・UI の使い勝手・チームへの馴染み方は、実際に自分の環境で動かしてみて初めて分かります。

「この検索ヒット精度で十分か」「チームメンバーが拡張機能を入れてくれるか」は、触れて初めて判断できることです。
仕様書から推測するのと、実際の検索結果を見るのとでは、感覚がまるで違います。
30分で動かせるというのは、その「判断の入口」を下げるために重要なことです。

hister は MCP 連携をサポートしており、AI アシスタントから検索を呼び出す拡張余地があります。
これは公式ドキュメントにも明記されている機能です。
今すぐ使わなくても、「育てていける選択肢」として持てるのは悪くないです。

合わなければ捨てる、合えば育てる。
この判断を30分で下せることが、このツールの本質的な価値だと思っています。


株式会社ホコサキは山口県宇部を拠点に、Web 制作・業務システム開発・AI 活用支援・DX 推進を手がけています。
「とりあえず動かして判断したい」という場面でのプロトタイプ構築や、社内ツール整備の相談も対応していますので、気軽にお声がけください。
詳しくは お問い合わせページ からどうぞ。