リモートビルダー使用時にローカルレジストリが自動起動されない問題を解消
リモートビルダーでローカルレジストリを指定すると、レジストリコンテナが停止していた場合にデプロイが失敗していました。この PR は、レジストリ起動と認証を別々に制御し、コンテナの自動起動を保証するように変更しています。
背景
builder.remote とローカルレジストリ (registry.server: localhost:5555) を組み合わせてデプロイすると、レジストリコンテナが停止していると connection refused エラーが発生します。原因は、Kamal::Cli::Build が KAMAL.builder.login_to_registry_locally? という単一のフラグで、リモートビルダーの認証とローカルレジストリの起動の二つの意味合いを同時に制御していたことにあります。
このフラグは #1664 でリモートビルダーの認証をスキップするために変更されましたが、同時にローカルレジストリの起動ロジックも無効化してしまいました。その結果、リモートビルダーはレジストリコンテナが存在しない状態でプッシュを試み、dial tcp [::1]:5555: connect: connection refused というエラーになるケースが増えていました。
したがって、リモートビルダーでもローカルレジストリが常に起動していることを保証する必要があります。
技術的な変更
push メソッド内で setup_local_registry と login_to_registry_locally の呼び出し条件を分離しました。新しい条件は、KAMAL.registry.local? が真なら必ず setup_local_registry を実行し、KAMAL.registry.local? が偽かつ KAMAL.builder.login_to_registry_locally? が真の場合にのみ login_to_registry_locally を実行します。
元の実装では login_to_registry_locally if KAMAL.builder.login_to_registry_locally? が唯一のガードとなり、ローカルレジストリでもリモートビルダーでも同じフラグで制御されていました。このため、リモートビルダーがローカルレジストリを使うケースでレジストリ起動がスキップされていました。
新実装では setup_local_registry が KAMAL.registry.local? のみで判定され、レジストリがローカルかどうかに関係なく必ず起動処理が走ります。起動処理は docker start kamal-docker-registry || docker run … registry:3 という 冪等 なコマンドであり、毎回呼び出しても安全です。
login_to_registry_locally はローカルレジストリ以外でだけ実行され、リモートレジストリに対する docker login のみを担当します。これにより #1664 の「リモートビルダーでローカル認証を行わない」方針はそのまま保持されます。
設計判断
責務の分離 を明確にした点が本変更の核心です。レジストリの起動と認証は本質的に別の操作であるにもかかわらず、以前は同一フラグで管理されていました。フラグを分割して条件式を明示的に書くことで、意図しない副作用を防ぎ、コードの可読性も向上しました。
また、後方互換性 を重視し、既存の KAMAL.builder.login_to_registry_locally? の挙動は変更せずに残しました。ローカルレジストリを利用する従来のビルダー(ローカルビルダー)でも同様に setup_local_registry が呼び出され、動作に差異はありません。
テスト面でも、リモートビルダー+ローカルレジストリの組み合わせでレジストリコンテナが起動することを検証する新規テストを追加し、既存のリモートビルダー認証スキップテストが引き続き通過することを確認しています。この回帰テストにより、将来的な変更で同様の問題が再発するリスクを低減しています。
まとめ
push のフローを setup_local_registry と login_to_registry_locally に分割し、ローカルレジストリの自動起動を保証することで、リモートビルダー使用時のデプロイ失敗を防止しました。結果として、ローカルレジストリとリモートビルダーの組み合わせが安全に利用できるようになり、既存の認証回避ロジックはそのまま維持されています。