プライベートDockerレジストリへの認証対応
Onceがプライベートコンテナレジストリからのイメージプルをサポートしました。ローカルのDockerクレデンシャル設定を自動解決することで、ghcr.io・GCR・ECRなどのプライベートレジストリからも、ユーザーが追加設定なしにデプロイできるようになります。
背景
これまでのOnceは、イメージプル時に常に匿名アクセスを使用していたため、プライベートレジストリからのデプロイが認証エラーで失敗していました。PRの説明によれば、DHHのプレゼンテーション中にもこの問題が顕在化したとのことです。
問題の根源は internal/docker/application.go の pullImage メソッドにあり、image.PullOptions{} をそのまま(空のまま)渡していたため、レジストリ認証情報が一切送られていませんでした。docker pull コマンドは ~/.docker/config.json のクレデンシャルを自動的に使用しますが、Onceはその仕組みを利用していませんでした。
技術的な変更
変更の中心は新規追加された internal/docker/registry_auth.go と、それを呼び出すように修正された pullImage メソッドの2点です。
pullImage の変更前後:
変更前:
reader, err := a.namespace.client.ImagePull(ctx, a.Settings.Image, image.PullOptions{})
変更後:
opts := image.PullOptions{RegistryAuth: registryAuthFor(a.Settings.Image)}
reader, err := a.namespace.client.ImagePull(ctx, a.Settings.Image, opts)
新たに追加された registryAuthFor 関数は、イメージ名を受け取り、対応するレジストリの認証情報をBase64エンコードしたJSON文字列として返します。
func registryAuthFor(imageName string) string {
ref, err := name.ParseReference(imageName)
if err != nil {
return ""
}
authenticator, err := authn.DefaultKeychain.Resolve(ref.Context())
if err != nil || authenticator == authn.Anonymous {
return ""
}
cfg, err := authenticator.Authorization()
if err != nil {
return ""
}
data, err := json.Marshal(cfg)
if err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(data)
}
認証解決には github.com/google/go-containerregistry の authn.DefaultKeychain を使用しており、これは docker CLI が採用しているのと同じクレデンシャル解決ロジックを持ちます。~/.docker/config.json に記載された静的クレデンシャルだけでなく、credHelpers で指定されたクレデンシャルヘルパーも自動的に利用されます。
依存関係としては、go.mod に以下のパッケージが追加されました:
-
github.com/google/go-containerregistry v0.21.3(直接依存) -
github.com/docker/cli v29.3.0+incompatible(間接依存) -
github.com/docker/docker-credential-helpers v0.9.3(間接依存) -
github.com/mitchellh/go-homedir v1.1.0(間接依存) -
github.com/sirupsen/logrus v1.9.4(間接依存)
設計判断
エラー時の安全な縮退設計が一貫して採用されています。registryAuthFor はイメージ名のパース失敗・クレデンシャル未設定・認証情報のシリアライズ失敗など、あらゆるエラーケースで空文字列を返します。Docker APIは RegistryAuth が空文字列の場合に匿名アクセスへフォールバックするため、パブリックイメージのデプロイは従来通り動作します。
クレデンシャル解決ロジックを自前実装せず authn.DefaultKeychain に委譲した点も重要な設計判断です。go-containerregistry は credHelpers(ECRやGCRの短期トークン取得)や credsStore といった複数のクレデンシャル解決方式をサポートしており、これを再実装するコストを避けつつ、docker pull と同等の認証体験を実現しています。
テストコード(registry_auth_test.go)では DOCKER_CONFIG 環境変数を使った設定の分離(isolateDockerConfig)や偽のクレデンシャルヘルパースクリプトのインストール(installFakeCredHelper)など、外部環境に依存しないテスト構造が採用されています。これにより、CI環境でも実際のレジストリへの接続なしにクレデンシャル解決ロジックを検証できます。
まとめ
registryAuthFor という単一関数の追加と pullImage への1行の変更により、Onceはdockerコマンドと同じクレデンシャル解決メカニズムを獲得しました。エラー時の安全なフォールバック設計により既存のパブリックイメージデプロイへの影響はなく、ユーザーは docker login 済みの環境であれば追加設定なしでプライベートレジストリからのデプロイが可能になります。