docker『Exec Format Error』の原因と対処法

docker『Exec Format Error』の原因と対処法

「実行形式が不正」の意味で、カーネルがそのファイルを実行可能バイナリ/スクリプトとして解釈できないときに発生。多くは“アーキテクチャ不一致(arm64上でamd64バイナリなど)”“Windows行末/UTF-8 BOM付きのエントリポイント”“シェル/インタプリタ不在”“実行ビット欠如・誤ったENTRYPOINT/CMD”が原因。まず終了直前のログと実体ファイルを検査し、アーキと起動スクリプトを正規化する。

症状と発生条件(まず把握)

# 典型メッセージ(コンテナ起動時/exec時)
standard_init_linux.go:xxx: exec user process caused: exec format error
exec /usr/local/bin/start.sh: exec format error
[8] System error: exec format error

・ホストCPUとイメージのCPUアーキが違う(例:M1/M2 Mac=arm64 なのに linux/amd64 専用バイナリ)。

・ENTRYPOINT/CMDがCRLFやBOM付きで、シバン行を正しく解釈できない。

・#!/bin/bash 指定だが bash が入っていない(Alpineなど)。

・実行権限がない/参照パスが間違っている/拡張子だけで実体がWindows可搬exe等。

最短切り分け(アーキと実体を特定)

# ホスト/VMのアーキ
uname -m            # x86_64, aarch64 など

# イメージとプラットフォームの関係
docker image inspect <image>:<tag> --format '{{.Architecture}}'
docker buildx imagetools inspect <image>:<tag>  # multi-arch manifestの有無

# ENTRYPOINT/CMDの実体と権限
docker image inspect <image>:<tag> --format '{{json .Config}}' | jq '.Entrypoint,.Cmd'
docker run --rm -it --entrypoint sh <image>:<tag> -lc 'ls -l /usr/local/bin && file /usr/local/bin/* | head'

アーキ不一致(arm64↔amd64)の根治:–platform と multi-arch

# その場しのぎ:ホストに合わせてpull/run
docker run --platform=$(uname -m | sed 's/x86_64/linux\/amd64/;s/aarch64/linux\/arm64/') <image>:<tag>

# エミュレーション(QEMU/binfmt)を整える
docker run --privileged --rm tonistiigi/binfmt --install all

# 正攻法:multi-archイメージをビルド&配信
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t repo/app:1.2.3 --push .

・チーム配布物は multi-arch 化して「Exec Format Error」を原理的に回避する。

シェル/インタプリタ不在(/bin/bash→/bin/sh など)

# Alpine等は bash 非搭載が普通。shebangを /bin/sh に変更するか bash を導入
# 1) ベースに合わせて shebang を修正
#!/bin/sh
# 2) どうしても bash が必要なら導入
RUN apk add --no-cache bash

・shebang(1行目)に存在しないパスを指定すると実行不能。/usr/bin/env 経由が堅牢。

CRLF / UTF-8 BOM / 実行ビット欠如の修正(ENTRYPOINTスクリプト)

# スクリプトの改行/BOM/実行ビットを統一(Dockerfile)
FROM alpine:3.20
RUN apk add --no-cache dos2unix
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN dos2unix /usr/local/bin/entrypoint.sh \
 && sed -i '1s/^\xEF\xBB\xBF//' /usr/local/bin/entrypoint.sh \
 && chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

・Gitの属性設定でCRLF混入を抑止:*.sh text eol=lf。BOMは常に除去するのが無難。

exec形式CMD/ENTRYPOINTに統一(シグナル/引数の混乱も同時解決)

# NG(shell形式。改行/引用/環境で壊れやすい)
CMD npm start
# OK(exec形式。カーネルが直接実行)
CMD ["npm","start"]

# ENTRYPOINTも同様にexec形式を推奨
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

バイナリ自体の形式確認(間違ったOS/CPU/リンク方式)

# バイナリのヘッダを確認
docker run --rm -v "$PWD:/w" alpine sh -lc 'apk add --no-cache file >/dev/null; file /w/app'
# 例: ELF 64-bit LSB executable, x86-64 ... → arm64ホストではエミュレーション/再ビルドが必要

# Goなどのクロスコンパイルで正しいターゲットへ
GOOS=linux GOARCH=arm64 go build -o app-arm64 .

・glibc前提バイナリをAlpine(musl)で実行して別のエラーを誘発することも。必要ならglibc互換層を導入するかベースをUbuntu/Debianへ。

「ファイルではなくディレクトリ」/パスミスの検査

# ENTRYPOINT/CMDがディレクトリを指していないか/存在するか
docker run --rm <image> sh -lc 'test -f /usr/local/bin/start || { ls -l /usr/local/bin; exit 1; }'

・build時のCOPY先やシンボリックリンクの解決ミスでも同様の症状になる。

Windows/macOS製バイナリ混入の検出(CIでのガード)

# CIのテストジョブ例:Linux用でない実行物をfailさせる
find . -type f -perm -0100 -exec file {} \; | \
  grep -E 'PE32|Mach-O' && { echo "Non-Linux binary found"; exit 1; } || true

・配布ZIPをそのままCOPYしてしまい、Windows用exeが紛れ込む事故を防止。

Kubernetesでの症状(armノード混在/イメージ選択ミス)

# ノードのアーキを一覧し、スケジューリング先を確認
kubectl get nodes -o wide
# マニフェスト側で正しいイメージ/マニフェストを参照(multi-arch推奨)
kubectl set image deploy/app app=repo/app:1.2.3

・mixed cluster ではmulti-arch manifest必須。ノードごとのpull結果に注意。

トラブル時の最小再現とデバッグ手順

# 1) 同イメージをシェルで起動し、実行ファイルの内容を確認
docker run --rm -it --entrypoint sh <image>:<tag> -lc '
  set -e
  which app || { echo "app not found"; exit 127; }
  file $(which app)
  head -c 3 /usr/local/bin/entrypoint.sh | od -An -tx1  # BOM検査
  ls -l /usr/local/bin/entrypoint.sh
'

# 2) --platform指定で動作差分を切り分け
docker run --rm --platform=linux/amd64 <image>:<tag> true
docker run --rm --platform=linux/arm64 <image>:<tag> true

NG→OK早見(クイック修正集)

# NG: arm64ホストにamd64単体イメージ → OK: --platform or multi-arch配布
docker run --platform=linux/arm64 <image>
docker buildx build --platform linux/amd64,linux/arm64 ...

# NG: #!/bin/bash(未導入) → OK: /usr/bin/env bash または /bin/sh に変更
#!/usr/bin/env bash

# NG: CRLF/BOM → OK: dos2unix + BOM除去 + chmod +x
RUN dos2unix /usr/local/bin/entrypoint.sh && sed -i "1s/^\xEF\xBB\xBF//" /usr/local/bin/entrypoint.sh && chmod +x /usr/local/bin/entrypoint.sh

# NG: shell形式CMD → OK: exec形式CMD/ENTRYPOINT
CMD ["node","server.js"]

# NG: 実体がMac/Winバイナリ → OK: Linux向けに再ビルド or multi-arch提供
GOOS=linux GOARCH=amd64 go build ./...

チェックリスト(上から順に潰す)

1) ホスト uname -m とイメージの .Architecture / manifest を照合したか
2) ENTRYPOINT/CMD は exec形式か/指している実体は存在しているか
3) スクリプトは LF・BOM無・chmod +x・正しい shebang になっているか
4) 必要なシェル/インタプリタ(/bin/sh, bash, python 等)はベースに入っているか
5) バイナリの file 出力が Linux向け/正しいアーキか(Mach-O/PE32 が紛れていないか)
6) Alpine(musl) と glibc 依存の差異を理解し、必要ならベースを変更したか
7) K8s/CI では multi-arch イメージ/–platform で混在環境に対応したか
8) それでも解決しない場合、–entrypoint sh で中に入り現物を直接検査したか