
Windows PowerShell で aws ecr get-login-password | docker login が 400 Bad Request になる原因は BOM だった
はじめに
こんにちは、海江田です。
個人で作っている「GPU で LLM の活性値を取り出す仕組み」を EKS で動かすために、自作のコンテナイメージを Windows の PowerShell から ECR に push しようとしていました。
ところが、AWS CLI の aws ecr get-login-password を docker login にパイプで渡したところ、400 Bad Request で失敗してしまいました。
不思議なことに、同じコマンドを Git Bash で打つと成功します。原因を追いかけてみたら、PowerShell のパイプが、パスワードの先頭に BOM(目に見えない3バイト)を付けていた ことが分かりました。
この記事では、原因にたどり着くまでの切り分けと、対処の方法をまとめます。
結論
- Windows PowerShell 5.1 で、ターミナルのコードページが 65001(UTF-8)のとき、外のプログラムにパイプで文字列を渡すと、先頭に UTF-8 の BOM(
EF BB BF)が付きます - BOM の出どころは、コンソールの文字コード
[Console]::InputEncodingでした docker login --password-stdinは末尾の改行は取り除きますが、先頭の BOM は取り除かないので、BOM 付きのパスワードで ECR の認証に失敗して400 Bad Requestになります- 対処は、次のどれかです
cmd /c "..."でコマンドを包む[Console]::InputEncodingを BOM なしの UTF-8 にする- Git Bash など、bash で実行する
なお、同じ 400 Bad Request でも、ほかの原因で起きることもあります。この記事は「コードページが 65001 の Windows PowerShell 5.1」で起きた場合の話です。
環境
- Windows 11 Home
- Windows PowerShell 5.1.26100
- AWS CLI v2
- Docker Desktop for Windows(Docker CLI 29.8.2)
- Python(バイト列を確かめるのに使いました)
- ターミナルのコードページ: 65001(UTF-8)
日本語版 Windows で PowerShell を開くと、コードページは 932 になっていることが多いと思います。その場合、この問題は起きませんでした(後述)。
今回 65001 になっていたのは、PowerShell のプロファイルやレジストリで設定していたからではなく、使っていたアプリに内蔵のターミナルが、65001 で PowerShell を起動していたためでした(プロファイルはすべて未作成、レジストリのコードページや cmd の AutoRun の設定もありませんでした)。
起きたこと
ECR のリポジトリにログインするため、次のコマンドを実行しました(アカウント ID は例です)。
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
返ってきたのはこれです。
Error response from daemon: login attempt to https://123456789012.dkr.ecr.us-east-1.amazonaws.com/v2/ failed with status: 400 Bad Request
aws ecr get-login-password だけを実行すると、トークンはちゃんと返ってきます。トークンを作るところまでは問題なさそうでした。
原因を切り分ける
1. Git Bash では成功する
まず、同じコマンドを Git Bash で実行してみました。
Login Succeeded
あっさり成功しました。AWS の認証やトークンそのものは正しくて、PowerShell で実行したときだけ失敗しているようです。
2. 最初は「末尾の改行」を疑った
PowerShell のパイプでよく聞くのが、「渡す文字列の末尾に改行が付く」という話です。調べてみると、このエラーについても「改行のせいで失敗する」という説明が見つかりました。
なので最初は、改行が原因だろうと思っていました。ただ、それなら何が渡っているのか、実際に見てみようと思いました。
3. パイプで渡っている中身を見る
受け取った中身をそのまま表示する小さな Python を用意して、PowerShell のパイプが何バイトを渡しているかを確かめます。まずはトークンの代わりに、A を 2,156 文字並べた文字列を渡しました。
("A" * 2156) | python -c "import sys; d = sys.stdin.buffer.read(); print(len(d), d[:4], d[-4:])"
2161 b'\xef\xbb\xbfA' b'AA\r\n'
2,156 文字を渡したのに、届いたのは 2,161 バイトでした。増えた5バイトの内訳はこうです。
- 先頭に
\xef\xbb\xbf(3バイト) - 末尾に
\r\n(2バイト)
末尾の改行は予想どおりでしたが、先頭にも何か付いています。この EF BB BF は BOM(Byte Order Mark)です。もともとは文字のバイトの並び順を示すための印ですが、UTF-8 では「この文字列は UTF-8 で書かれています」という目印として使われます。ただ、パスワードの一部として扱われると、まったく別の文字列になってしまいます。
本物のトークンでも確かめました。トークンの中身は表示せず、長さと先頭・末尾のバイトだけを見ます。
aws ecr get-login-password --region us-east-1 | python -c "import sys; d = sys.stdin.buffer.read(); print(len(d), d[:3], d[-2:])"
2517 b'\xef\xbb\xbf' b'\r\n'
このときのトークンは 2,512 文字(トークンの長さは毎回変わります)で、届いたのは 2,517 バイト。本物のトークンでも、先頭に BOM の3バイト、末尾に改行の2バイトが付いていました。
※ python と打って Microsoft Store が開く場合は、Python が入っていないか、「アプリ実行エイリアス」に横取りされています。py や、仮想環境の Python を使ってください。
4. cmd のパイプと比べる
同じことを cmd のパイプでも試しました。
cmd /c "echo AAAA| python -c ""import sys; d = sys.stdin.buffer.read(); print(len(d), d[:4], d[-4:])"""
6 b'AAAA' b'AA\r\n'
cmd では BOM は付かず、末尾の改行だけが付いていました。
ここで、最初の予想がくずれました。cmd 経由の docker login は成功するのに、cmd でも改行は付いています。つまり、改行は原因ではありません。
Docker CLI のソースコード(readSecretFromStdin)を見ても、--password-stdin で読み込んだ内容は、末尾の改行を1つだけ取り除いて、それ以外はそのまま使う 作りになっていました。BOM を取り除く処理はありません。PowerShell と cmd のちがいは、先頭の BOM があるかどうか、ということになります。
5. コードページだけを変えて比べる
では、なぜ BOM が付くのか。ターミナルのコードページを確かめてみると、65001(UTF-8)になっていました。
chcp
現在のコード ページ: 65001
コードページだけの違いを比べるために、プロファイルを読み込まない新しい PowerShell(-NoProfile)を、65001 と 932(日本語版 Windows の標準)でそれぞれ起動して、同じ確認をしました。
# 65001
cmd /c "chcp 65001 >nul & powershell -NoProfile -Command ""('A' * 10) | python -c 'import sys; d = sys.stdin.buffer.read(); print(len(d), d[:4], d[-4:])'"""
# 932
cmd /c "chcp 932 >nul & powershell -NoProfile -Command ""('A' * 10) | python -c 'import sys; d = sys.stdin.buffer.read(); print(len(d), d[:4], d[-4:])'"""
15 b'\xef\xbb\xbfA' b'AA\r\n'
12 b'AAAA' b'AA\r\n'
65001 だと BOM が付き、932 だと付きませんでした。
原因
Windows PowerShell 5.1 は、外のプログラムにパイプで文字列を渡すとき、文字列をバイト列に変換します。このとき関わりそうな設定は $OutputEncoding と、コンソールの文字コード [Console]::InputEncoding の2つです。65001 の新しい PowerShell で、それぞれの中身を見てみました。
cmd /c "chcp 65001 >nul & powershell -NoProfile -Command ""'OutputEncoding: ' + `$OutputEncoding.EncodingName + ' / BOM: ' + ((`$OutputEncoding.GetPreamble() | % { `$_.ToString('X2') }) -join ' '); 'Console.InputEncoding: ' + [Console]::InputEncoding.EncodingName + ' / BOM: ' + (([Console]::InputEncoding.GetPreamble() | % { `$_.ToString('X2') }) -join ' ')"""
OutputEncoding: US-ASCII / BOM:
Console.InputEncoding: Unicode (UTF-8) / BOM: EF BB BF
$OutputEncoding は Windows PowerShell 5.1 の既定どおり US-ASCII で、BOM はありません。一方、[Console]::InputEncoding は、コードページが 65001 だと BOM 付きの UTF-8 になっていました。
どちらが BOM の出どころなのかを、本物の docker login で確かめました(どれも -NoProfile の新しい PowerShell です)。
| コードページ | 変えた設定 | 結果 |
|---|---|---|
| 65001 | なし | 400 Bad Request |
| 65001 | [Console]::InputEncoding だけ BOM なしの UTF-8 に |
Login Succeeded |
| 65001 | $OutputEncoding だけ BOM なしの UTF-8 に |
400 Bad Request |
| 932 | なし | Login Succeeded |
[Console]::InputEncoding を BOM なしにしたときだけ、成功しました。BOM の出どころは [Console]::InputEncoding で、docker login --password-stdin が BOM を取り除かないため、BOM 付きのパスワードが ECR に送られて、400 Bad Request になっていました。
対処
方法1: cmd /c で包む
一番手軽です。パイプの部分を cmd に任せるので、BOM が付きません。
cmd /c "aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com"
Login Succeeded
方法2: [Console]::InputEncoding を BOM なしの UTF-8 にする
PowerShell のまま実行したい場合は、コンソールの文字コードを BOM なしの UTF-8 にします。
[Console]::InputEncoding = New-Object System.Text.UTF8Encoding $false
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
Login Succeeded
この設定は、そのターミナルを閉じるまで有効です。
なお、$OutputEncoding だけを変えても直りませんでした(上の表)。ただ、プロファイルなどで $OutputEncoding を BOM 付きの UTF-8([System.Text.Encoding]::UTF8)にしている場合は、そちらからも BOM が付くので、$OutputEncoding も BOM なしにしてください。
方法3: bash で実行する
Git Bash など、bash で実行すれば BOM は付きません。今回も Git Bash では最初から成功していました。
やらないほうがいい方法
パスワードを -p で直接渡す方法もありますが、パスワードがコマンドの履歴やプロセスの一覧に残ってしまいます。Docker 自身も --password-stdin を使うよう警告を出すので、おすすめしません。
試していないこと
この記事で確かめたのは、Windows PowerShell 5.1 と、コードページ 65001・932 の組み合わせだけです。次のものは試していません。
- AWS Tools for PowerShell の
(Get-ECRLoginCommand).Password | docker login ...:AWS のドキュメントで PowerShell 向けに案内されている方法です。これも PowerShell のパイプで docker に渡すので、同じ条件では同じことが起きるかもしれません - PowerShell 7:
$OutputEncodingの既定が BOM なしの UTF-8 になっているので、結果が変わるかもしれません - 65001・932 以外のコードページ
まとめ
- Windows PowerShell 5.1 + コードページ 65001(UTF-8)の環境では、
[Console]::InputEncodingが BOM 付きの UTF-8 になり、パイプで渡す文字列の先頭に BOM が付く docker login --password-stdinは BOM を取り除かないので、400 Bad Requestになるcmd /cで包むか、[Console]::InputEncodingを BOM なしの UTF-8 にするか、bash で実行すれば解決する
最初は「どうせ改行でしょ」と思っていたのですが、実際にバイト列を見てみたら、犯人は先頭に隠れていた3バイトでした。手元で確かめてみてよかったです。
同じエラーで困っている方の参考になればうれしいです。
参考
- docker/cli: cli/command/registry/login.go(readSecretFromStdin)
- about_Preference_Variables($OutputEncoding)- PowerShell 5.1 | Microsoft Learn
- about_Character_Encoding - PowerShell 7.5 | Microsoft Learn
- プライベートレジストリの認証 - Amazon ECR ユーザーガイド
- Using AWS's ECR get-login-password and --password-stdin with Powershell - Docker Community Forums








