SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-26

HalcyonでJellyfinを90年代のビデオ店にする方法

HalcyonはJellyfinのライブラリをブラウザーで歩ける1990年代のレンタル店に再構成します。Dockerコマンド、リバースプロキシ、GPUや開発体制の注意点を確認できます。

Halcyon が Jellyfin ライブラリに行うこと

Halcyon Video は、ブラウザー上で Jellyfin ライブラリを歩いて回れる 1990 年代のビデオ店として描画します。所有している各作品が棚のケースになります。蛍光灯の下で通路を歩き、ケースを取り出して裏返し、背面の仕様を確認してからカウンターへ運ぶと、再生が始まります。再生の開始、進行状況、停止は Jellyfin に送信されるため、レジューム位置と視聴履歴は正しく維持されます。

Halcyon は Jellyfin API 経由で既存の Jellyfin サーバーを読み取り、独自のライブラリは保持しません。このガイドでは、Jellyfin がすでに稼働し、問題なくスキャンできていることを前提とします。そうでない場合は、まず VPS 上で Jellyfin をメディアサーバーとして構成する を設定し、通常の Web クライアントでライブラリが正しく表示される状態になってから戻ってきてください。これは、すでにライブラリがあるために導入するものです。セルフホスティングの一覧に別のサービスが必要だから導入するものではありません。

このプロジェクトは GPL-3.0 で、1 人の開発者によって作成されています。README には、pull request を受け付けないことが明記されています。開発は速いペースで進み、リグレッションを確認する 2 人目のメンテナーもいないため、他の人にこのビデオ店を見せる前にイメージのバージョンを固定してください。方法は最後のセクションで説明します。

レンダリングはどこで行われますか?

ブラウザー内です。Halcyon は、WebGL(web graphics library。ブラウザーから GPU にアクセスするためのインターフェイス)を介して 3D グラフィックスを描画する JavaScript ライブラリ、three.js を基盤とする Vite および TypeScript アプリケーションです。ストアのジオメトリとボックスアートは、画面を表示しているマシン上で合成されます。

コンテナが行う処理はごくわずかです。npm run serve を実行します。これは vite preview --port 1420 --strictPort --host です。また、ビルド済みファイルと少数の小さなミドルウェアルートを提供します。Halcyon はトランスコードを行わず、サーバー上でゲームエンジンも実行しません。

したがって、GPU に関する問題はクライアント側の問題です。小規模な VPS でも問題なく提供できます。提供する処理は、HTTP 経由で静的ファイルを配信するだけだからです。ブラウザーを実行する laptop、tablet、または television によって、ストアが滑らかに動作するか、処理が遅くなるかが決まります。

この原則に反する機能が 1 つあります。Remote Play はサーバー上で headless Chromium のインスタンスを起動し、レンダリング済みのストアを WebRTC(web real time communication)経由で phone または set top box にストリーミングします。この経路ではサーバー上でレンダリングが行われます。デフォルトでは 2 インスタンスに制限されており、REMOTE_PLAY_MAX_INSTANCES で変更できます。マッピングされた /dev/dri デバイスがない場合、これらのインスタンスは CPU 上でレンダリングするため、2 コアの VPS では閲覧者が 1 人増えるだけでも負荷を感じます。

ライブラリからストアが読み取る情報

通路の構成には、Jellyfin 独自の構造が使われます。Halcyon はライブラリとジャンルからセクションを配置し、BoxSets の情報を基に続編をまとめます。各ケースの背面に表示される仕様は、Jellyfin がすでに保持している MediaStreams メタデータから取得されます。そのため、Jellyfin にない情報は棚にも表示されません。

つまり、このストアはメタデータを正確に反映します。アートワークとジャンルが入力済みの Docker Compose 上の arr スタックからライブラリを構築している場合、一般的な名前のファイルだけを並べたフォルダーよりも、ここでの表示が大幅に良くなります。写真ライブラリも、インデックスを作成したソフトウェアに同じように依存します。同じサーバーに保存された静止画について、PhotoPrism と Immich を比較する際には、この点を覚えておくとよいでしょう。

インストール前にビデオストアのデモを試す

プロジェクトでは、合成ライブラリを使用して動作するストア全体をホストされたデモで公開しています。自分の環境でも、任意の Halcyon URL の末尾に ?demo=1 を追加すると同じデモを利用できます。

ハードウェアのテストとして使用してください。デモライブラリには約 2,000 件のタイトルがあり、ブラウザーのメモリをおよそ 2 GB 使用します。これは、個人用ライブラリの大半より負荷が高い構成です。閲覧に使用する予定のデバイスでデモの表示が途切れる場合、自分のライブラリでも同じ問題が発生します。その場合の対策は、より大きな VPS へ移行することではなく、以下で説明する 2.5D モードを使用することです。

Docker で実行する

これは upstream がドキュメントで示しているコマンドです。

docker run -d --name halcyon --network host --restart unless-stopped \
  ghcr.io/halcyon-video/halcyon-video

次に、起動したことを確認します。

docker logs halcyon
curl -I http://127.0.0.1:1420

ログには、preview server が port 1420 で listening していることが表示されます。また、curlHTTP/1.1 200 OK に応答することも確認します。数秒以内に終了する container では、ほとんどの場合 port が原因です。--strictPort は、1420 が使用中でも server が 1421 へ移行せず、そのまま停止することを意味します。

--network host は Remote Play 用であり、store 用ではありません。WebRTC では、stream を要求する device にマシンの実アドレスを通知する必要があります。デフォルトの Docker bridge 配下では、container が認識できるのは自身の 172.x アドレスだけです。ネットワーク上の phone からは到達できないため、stream は接続されません。browser で store だけを使用する場合は、port を publish してください。

docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
  ghcr.io/halcyon-video/halcyon-video

VPS では、これがより適切なデフォルトです。host networking により、container がマシン上のすべての interface(public interface を含む)を使用するためです。この設定の詳細なトレードオフについては、VPS で Docker を実行する を参照してください。--restart unless-stopped により、reboot 後も store が起動します。これは boot 時に起動する Compose service と同じ考え方です。

repository を clone して docker compose up -d を実行すると、代わりに image がローカルで build されます。commit 済みの Compose file はデフォルトで source から build し、事前に build された image: の行を comment out しています。Compose で公開済みの image を使用する場合は、その行の comment を解除してください。

2026 年 8 月時点で、1 つ制限があります。公開済みの image は linux/amd64 のみです。multi architecture push の arm64 側は emulation 環境で失敗し、native arm runner の準備待ちです。arm64 VPS では pull が no matching manifest for linux/arm64/v8 in the manifest list entries で失敗します。この場合は、clone した repository から build してください。

Jellyfin サーバーを指定する

http://<host>:1420 を開き、Jellyfin サーバーのアドレス、ユーザー名、パスワードでログインします。リポジトリ内の .env.local.example ファイルはローカル開発専用です。Vite は VITE_ を先頭に持つ変数をクライアント側のコードに公開するため、そこに記述した Jellyfin のパスワードは、すべての訪問者がダウンロードする JavaScript バンドルにコンパイルされます。他のユーザーから到達できるサーバーでは、インターフェースからログインしてください。

ブラウザーは Jellyfin と直接通信します。Halcyon のコンテナは Jellyfin API をプロキシしません。デバッグを始める前に、次の2点を把握しておく必要があります。

1つ目は、Jellyfin が Halcyon を提供する VPS からだけでなく、ブラウザーからも到達可能でなければならないことです。127.0.0.1:8096 にバインドした Jellyfin はローカルテストには適していますが、それ以外のユーザーには棚が空のまま表示されます。

2つ目は、この呼び出しが Halcyon のアドレスから Jellyfin のアドレスへのクロスオリジン通信になることです。Jellyfin はデフォルトで API リクエストに Access-Control-Allow-Origin: * で応答するため、追加設定なしで動作します。この設定を限定している場合、または Jellyfin API の前段に認証プロキシを置いている場合は、ブラウザーのコンソールに blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource が表示され、ストアは空の棚を表示します。

認証を前段に置いたリバースプロキシの背後で運用する

vite preview はプレビュー用サーバーです。TLS(トランスポート層セキュリティ)を終端せず、独自のアクセス制御も備えていないため、インターネットに公開する場合は nginx または Caddy の背後に配置します。

server {
  listen 443 ssl;
  server_name halcyon.example.com;

  location / {
    proxy_pass http://127.0.0.1:1420;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

コンテナの前段にドメイン名を置く場合は、設定がもう 1 つ必要です。Halcyon は DNS リバインディング対策として、localhost、未加工の IP アドレス、実行先マシンの名前に応答します。コンテナ内では実行先マシンがコンテナ自身であるため、そのホスト名は使用するマシンの名前ではありません。halcyon.example.com として到着したリクエストは拒否され、拒否したホスト名がレスポンスに示されます。その名前を追加してください。

docker run -d --name halcyon -p 127.0.0.1:1420:1420 --restart unless-stopped \
  -e HALCYON_ALLOWED_HOSTS=halcyon.example.com \
  ghcr.io/halcyon-video/halcyon-video

値はコンマ区切りです。.example.com のように先頭にドットを付けると、サブドメインにも一致します。all を指定するとチェックが無効になります。all は、外部から到達できないマシンでのみ使用してください。

ストアを https:// で提供する場合、ログイン時に入力する Jellyfin のアドレスも https:// にする必要があります。HTTPS ページから実行した通常の http:// API 呼び出しはブラウザーによってブロックされ、コンソールには Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource と表示されます。Halcyon 内には説明が表示されないため、ログインは単に失敗します。両方を TLS 経由で提供するか、プライベートネットワーク内で両方を通常の HTTP のまま運用してください。

次に認証です。ストアは Jellyfin の認証情報を要求するため、URL を見つけた第三者にはログイン画面が表示されます。ただし、1 つの機能によってこの状態が変わります。Settings、Connection の順に開いて Remote Play を有効にすると、Jellyfin のセッションがサーバーに渡され、/remote.html にアクセスした訪問者が実際のライブラリを使う自分専用のインスタンスを取得します。これがこの機能の目的であり、インターネットと映画の間にあるのは URL の秘匿性だけになります。Remote Play を有効にする場合は、自己ホスト型 SSO ゲートウェイとしての Authentik を使ってサイト全体の前段にシングルサインオンを配置するか、公開ホスト名を廃止し、wg-easy で管理する WireGuard トンネル 経由でストアにアクセスしてください。

関連する点が 2 つあります。リバースプロキシが転送するのはストアだけです。Remote Play のストリームは UDP 上の WebRTC であり、HTTP プロキシを経由しません。そのため、3478/udp と、組み込み TURN リレーを使用する場合は 49200 から 49260/udp に、専用の経路が必要です。また、上記の通常の docker run にはボリュームがないため、Remote Play のシードは docker rm 後に保持されません。Compose ファイルが /datahalcyon-data ボリュームをマウントし、まさにこの理由で REMOTE_PLAY_SEED/data/remote-play-seed.json に設定しています。

ストアの動作が悪い場合の対処

Halcyon は必要なときに描画します。アイドル状態のストアはフレームを合成せず、ウィンドウのフォーカスが外れるとアニメーションループを停止します。そのため、タブを開いたままにしてもノート PC のバッテリーを消耗させません。これは、性能がぎりぎりのマシンには有効です。ただし、ストアをまったく描画できないマシンには効果がありません。

そのようなクライアント向けに、WebGL を使わず、単純な HTML と CSS で動作する 2.5D モードがあります。Raspberry Pi のような小型のハードウェアも対象です。設定または電源メニューから、ページを再読み込みせずに 3D と 2.5D を切り替えられます。そのため、同じデバイスで両方を試すのに数秒しかかかりません。得られる結果については現実的に考えてください。作者はフラットモードを粗削りで、まだ開発途中だと説明しています。性能の低いクライアント向けのフォールバックとして扱ってください。

クライアントの性能が 3D ストアに足りない場合、障害は明確に現れます。棚の読み込み中に、タブが自動的に再読み込みされたり、ブラウザーが WebGL コンテキストの喪失を報告したりします。そのデバイスではライブラリを減らすのではなく、2.5D に切り替えてください。

イメージを固定し、pull 前に確認する

この点は軽視しないでください。v0.1.0 から v0.3.1 までのタグはすべて数日以内に公開され、v0.2.1 が存在するのは v0.2.0 のイメージの push に失敗したためです。上流プロジェクトへのバグ報告は歓迎されますが、パッチは受け付けられません。そのため、リリース系列は 1 人の作業中の状態です。

docker pull を習慣にして latest を実行すると、通常の火曜日でもストアの内容が変わる可能性があります。移動できない唯一の参照である digest で固定してください。

docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1

これにより、タグの背後にある digest が表示されます。タグの代わりにこの値を使用してください。

docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
  ghcr.io/halcyon-video/halcyon-video@sha256:747dcc821a3d2fa318b50e76024783c1835609047e84f502e23d021bc1898b20

この digest は 2026 年 8 月 10 日時点では 0.3.1 でした。コピーせず、現在の値を自分で確認してください。また、更新する前にリリースノートを読んでください。この場合、パッチリリースに修正だけでなく、ストアレイアウトの変更が含まれることもあります。

FAQ

Halcyon を VPS で使用する場合、GPU は必要ですか?

通常の使用では必要ありません。ストアはブラウザー上で three.js によって描画されるため、レンダリングはクライアントマシンが行い、コンテナは port 1420 で静的ファイルを提供するだけです。例外は Remote Play です。Remote Play はサーバー上で headless Chromium を実行し、その結果をストリーミングします。この処理は、ハードウェアアクセラレーションのために /dev/dri をコンテナへ割り当てない限り、CPU でレンダリングされます。

Halcyon をインターネット上に公開できますか?

認証の背後に配置する場合に限り、可能です。ストアは Jellyfin の認証情報を要求しますが、Remote Play を有効にすると Jellyfin のセッションがサーバーに渡されます。そのため、/remote.html を読み込んだユーザーは、ログインしなくても実際のライブラリのインスタンスを利用できます。シングルサインオンに対応したリバースプロキシを前段に配置するか、ホスト名をパブリック DNS に登録せず、VPN 経由でストアにアクセスしてください。

ログイン後も棚が空なのはなぜですか?

ブラウザーは Jellyfin API を直接呼び出すため、Jellyfin は VPS からだけでなく、ブラウザーからも到達できなければなりません。ブラウザーのコンソールを開いてください。blocked by CORS policy は、Jellyfin が Halcyon のアドレスからのリクエストを受け付けていないことを示します。Mixed Content メッセージは、ページが HTTPS である一方、入力した Jellyfin のアドレスが通常の HTTP であることを示します。

--network host は必要ですか?

Remote Play の場合に限り必要です。WebRTC はマシンの実際のアドレスを通知する必要がありますが、Docker bridge の背後では、コンテナが提供できるのはネットワーク上の電話から到達できない 172.x アドレスだけです。ブラウザーでストアを閲覧するだけなら -p 1420:1420 で動作し、ホストの公開範囲も大幅に小さくできます。

どの image tag を使用すべきですか?

latest ではなく digest を固定してください。docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1 があるバージョンの digest を確認して、その digest を使用します。リリースノートを読んでから更新してください。2026 年 8 月時点では公開されている image は linux/amd64 のみであるため、arm64 ホストでは clone から docker compose up -d を使用してビルドする必要があります。