- Published on
Docker buildx 멀티아키 이미지 exec format error 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에 배포하자마자 컨테이너가 바로 죽고 로그에 exec format error가 찍히면, 대부분은 “이미지 아키텍처와 실제 실행 환경 아키텍처가 다르다”는 뜻입니다. 문제는 이게 단순히 arm64/amd64 불일치만이 아니라, 빌드 파이프라인의 기본 플랫폼, 베이스 이미지 선택, 엔트리포인트(특히 셸 스크립트) 인코딩/라인엔딩, QEMU 설정 등 여러 요소가 얽혀서 발생한다는 점입니다.
이 글에서는 Docker buildx로 멀티아키 이미지를 빌드할 때 exec format error를 재현하고, 어떤 단계에서 잘못된 아키텍처가 “섞여” 들어가는지 확인한 뒤, 확실하게 고치는 방법을 정리합니다.
배포 환경이 Kubernetes라면, 크래시 루프 상황이 함께 나타나는 경우가 많습니다. 증상 관찰과 진단 흐름은 Kubernetes CrashLoopBackOff 원인 12가지와 진단도 같이 참고하면 좋습니다.
exec format error가 의미하는 것
리눅스에서 exec format error는 커널이 실행 파일(또는 스크립트)을 로드하려 했는데 “실행 형식이 현재 아키텍처/로더 규칙에 맞지 않는다”고 판단했을 때 발생합니다. 컨테이너 관점에서 대표 원인은 아래 네 가지입니다.
- 이미지 플랫폼 불일치:
linux/amd64이미지를linux/arm64노드에서 실행(또는 반대) - 엔트리포인트/커맨드 불일치:
ENTRYPOINT가 특정 아키 전용 바이너리이거나, 잘못된 셸 지정 - 스크립트 문제: CRLF(Windows 줄바꿈) 또는 잘못된 shebang, 실행 권한 누락
- 에뮬레이션/QEMU 문제: 멀티아키 빌드를 했지만 런타임에서 기대한 플랫폼 매니페스트가 없거나, 단일 아키 이미지가 푸시됨
빠른 진단 체크리스트 (5분 컷)
아래 명령으로 “내가 실제로 어떤 아키 이미지를 푸시했고, 런타임은 무엇을 받고 있는지”를 먼저 확인합니다.
1) 원격 레지스트리의 매니페스트가 멀티아키인지 확인
docker buildx imagetools inspect your-registry/your-image:tag
출력에서 Manifests: 아래에 linux/amd64, linux/arm64가 모두 있어야 합니다. 하나만 있다면 멀티아키가 아니라 단일 아키로 푸시된 것입니다.
2) 로컬에서 특정 플랫폼으로 강제 실행해 보기
docker run --rm --platform linux/amd64 your-registry/your-image:tag uname -m
docker run --rm --platform linux/arm64 your-registry/your-image:tag uname -m
amd64면 보통x86_64arm64면 보통aarch64
둘 중 하나가 실행 자체가 안 되거나 exec format error가 나면, 해당 플랫폼용 레이어/바이너리가 잘못 들어갔을 가능성이 큽니다.
3) Kubernetes라면 노드 아키 확인
kubectl get nodes -o wide
kubectl get node your-node -o jsonpath='{.status.nodeInfo.architecture}'
노드가 arm64인데 이미지가 amd64 단일이면 거의 100% 재현됩니다.
가장 흔한 원인 1: docker build로 빌드하고 buildx로 푸시했다고 착각
CI에서 종종 이런 흐름이 나옵니다.
docker build로 로컬 아키(예:amd64) 이미지를 만들고- 태그만 바꿔서 푸시
- 멀티아키라고 믿고 배포
해결은 “처음부터 끝까지 buildx로 멀티아키 매니페스트를 만들고 푸시”입니다.
정석 빌드 명령
docker buildx create --name multi --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t your-registry/your-image:tag \
--push \
.
핵심은 --push입니다. --load는 로컬 도커 엔진으로 단일 아키 이미지를 로드하는 용도라 멀티아키에 부적합합니다(멀티 매니페스트를 로컬 엔진에 “그대로” 로드하는 형태가 제한적).
가장 흔한 원인 2: Dockerfile에서 플랫폼 변수를 잘못 사용
멀티아키 빌드에서 Dockerfile은 “빌드 머신”과 “타깃 플랫폼”을 구분해야 합니다. BUILDPLATFORM과 TARGETPLATFORM(또는 TARGETARCH)를 혼동하면, 다른 아키용 바이너리를 가져오거나 잘못 컴파일해 exec format error가 납니다.
잘못된 예(자주 보이는 실수)
- 빌드 컨테이너가
amd64인데arm64산출물을 만들려는 상황에서, 다운로드 URL/아티팩트를BUILDARCH로 선택
권장 패턴: --platform과 ARG를 명시적으로 사용
# syntax=docker/dockerfile:1.7
FROM golang:1.22 AS build
ARG TARGETOS
ARG TARGETARCH
WORKDIR /src
COPY . .
# 타깃 아키로 크로스 컴파일
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/app ./cmd/app
FROM gcr.io/distroless/static-debian12:nonroot
COPY /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
이렇게 하면 빌드 스테이지는 빌드 머신에 맞는 이미지로 돌면서도, 결과 바이너리는 TARGETARCH로 정확히 생성됩니다.
가장 흔한 원인 3: 엔트리포인트 스크립트가 CRLF이거나 shebang이 깨짐
멀티아키와 무관하게도 exec format error가 나는 케이스가 있습니다. 특히 entrypoint.sh 같은 파일이 Windows 줄바꿈(CRLF)으로 들어가면, 리눅스에서 인터프리터 경로가 \r을 포함한 것으로 인식되어 실행 실패가 납니다.
증상
- 로컬에서는 잘 되는데 CI에서 생성된 이미지가 특정 환경에서만 실패
- 로그에
exec format error또는no such file or directory가 섞여 나옴
해결 1) Git 설정으로 줄바꿈 통제
.gitattributes에 추가:
*.sh text eol=lf
해결 2) 이미지 빌드 시점에 강제로 LF로 변환
FROM alpine:3.20
WORKDIR /app
COPY entrypoint.sh /app/entrypoint.sh
RUN sed -i 's/\r$//' /app/entrypoint.sh && chmod +x /app/entrypoint.sh
ENTRYPOINT ["/app/entrypoint.sh"]
해결 3) shebang 확인
entrypoint.sh 첫 줄은 예를 들어 아래처럼 명확해야 합니다.
#!/bin/sh#!/usr/bin/env bash
그리고 베이스 이미지에 해당 셸이 실제로 존재해야 합니다. 예를 들어 distroless 계열은 셸이 없어서 스크립트 엔트리포인트 자체가 부적합합니다.
가장 흔한 원인 4: 베이스 이미지가 특정 아키만 지원
FROM someimage:tag가 멀티아키 매니페스트를 제공하지 않으면, buildx가 타깃 플랫폼별로 빌드할 때 한쪽이 깨지거나(빌드 실패), 운 좋게 빌드는 되지만 런타임에서 엉뚱한 레이어를 받는 문제가 생길 수 있습니다.
확인 방법
docker buildx imagetools inspect someimage:tag
linux/arm64가 없다면 arm 노드에서 실행할 때 문제가 날 수 있습니다.
대안
- 멀티아키 공식 이미지로 교체(예:
alpine,debian,ubuntu,amazonlinux등) - 태그를 바꿔 멀티아키 지원 버전 사용
- 직접 베이스를 멀티아키로 빌드(권장 난이도 높음)
QEMU/에뮬레이션 관련: 빌드는 되는데 실행이 안 되는 경우
x86_64 머신에서 arm64를 빌드하려면 보통 QEMU가 필요합니다. GitHub Actions 등에서는 세팅이 자동화되기도 하지만, 자체 러너나 온프레미스에서는 누락되기 쉽습니다.
QEMU 등록
docker run --privileged --rm tonistiigi/binfmt --install all
그 다음 빌더를 부트스트랩합니다.
docker buildx create --name multi --use
docker buildx inspect --bootstrap
주의
- QEMU는 빌드(에뮬레이션 실행) 성능이 느립니다. 가능하면 각 아키 네이티브 러너를 두거나, 크로스 컴파일 가능한 언어는 크로스 컴파일로 해결하는 편이 안정적입니다.
실전 예시: Node.js 앱 멀티아키 이미지 만들기
Node는 네이티브 애드온(node-gyp)이 섞이면 아키 의존성이 생깁니다. 멀티아키에서는 “각 플랫폼에서 의존성 설치가 수행되도록” 구성해야 합니다.
# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:20-alpine AS build
WORKDIR /app
COPY /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
그리고 빌드는 다음처럼:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t your-registry/your-node-app:tag \
--push \
.
네이티브 모듈이 있다면, npm ci 단계가 플랫폼별로 수행되도록(즉, 멀티아키 빌드의 각 플랫폼 컨텍스트에서) 동작해야 합니다. 위 예시는 buildx가 플랫폼별로 스테이지를 실행하므로 일반적으로 안전합니다.
배포 파이프라인(Jenkins)에서 자주 터지는 포인트
Jenkins에서 Docker 소켓 권한/에이전트 권한 문제로 빌드가 꼬이면, 의도치 않게 로컬 캐시 이미지가 푸시되는 등 “멀티아키가 아닌 결과물”이 나가기도 합니다. 아래 글도 함께 점검하면 CI 안정성이 올라갑니다.
Jenkins에서 buildx 사용 예(핵심만)
# 빌더 준비
docker buildx create --name multi --use || docker buildx use multi
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t your-registry/your-image:${GIT_COMMIT} \
-t your-registry/your-image:latest \
--push \
.
최종 확인: “정말로” 멀티아키가 배포됐는지 검증하는 방법
문제를 고쳤다고 생각해도, 마지막 확인을 자동화하는 게 좋습니다.
1) 레지스트리 매니페스트 검증(필수)
docker buildx imagetools inspect your-registry/your-image:tag
linux/amd64/linux/arm64가 모두 있는지 확인합니다.
2) 각 플랫폼에서 최소 실행 테스트
docker run --rm --platform linux/amd64 your-registry/your-image:tag /app --version
docker run --rm --platform linux/arm64 your-registry/your-image:tag /app --version
엔트리포인트가 스크립트라면 /bin/sh -c를 쓰고 싶겠지만, distroless라면 셸이 없으니 바이너리 직접 실행 형태로 테스트하는 것이 안전합니다.
3) Kubernetes에서 아키 별 스케줄링 확인(선택)
혼합 노드 클러스터라면 nodeSelector/affinity로 아키를 강제해 각각 띄워보면 확실합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-amd64
spec:
replicas: 1
selector:
matchLabels:
app: app-amd64
template:
metadata:
labels:
app: app-amd64
spec:
nodeSelector:
kubernetes.io/arch: amd64
containers:
- name: app
image: your-registry/your-image:tag
정리
exec format error는 결과적으로 “실행하려는 것과 실행하는 곳의 형식이 다르다”는 신호입니다. 멀티아키 환경에서는 특히 다음 순서로 접근하면 빠르게 끝납니다.
imagetools inspect로 레지스트리 매니페스트가 진짜 멀티아키인지 확인--platform으로 로컬 강제 실행 테스트- Dockerfile에서
BUILDPLATFORM/TARGETARCH사용이 맞는지 점검 - 엔트리포인트 스크립트의 CRLF/shebang/권한 확인
- 베이스 이미지의 멀티아키 지원 여부 확인
- 필요 시 QEMU/binfmt 설정 및 빌더 부트스트랩
이 흐름대로만 점검해도, “빌드는 성공했는데 배포하면 죽는” 멀티아키 특유의 함정을 대부분 제거할 수 있습니다.