Windows PowerShell で aws ecr get-login-password | docker login が 400 Bad Request になる原因は BOM だった

Windows PowerShell で aws ecr get-login-password | docker login が 400 Bad Request になる原因は BOM だった

Windows PowerShell のパイプで ECR にログインできない問題を調査したところ、パスワードの先頭に付く目に見えない BOM(3バイト)が犯人だったという話です。原因の切り分け方と、3つの対処法をまとめます。
2026.10.20

はじめに

こんにちは、海江田です。

個人で作っている「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バイトでした。手元で確かめてみてよかったです。

同じエラーで困っている方の参考になればうれしいです。

参考

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事