로컬(M2 Mac)에서 빌드하고 푸시한 이미지를 GitHub Actions Ubuntu 러너에서 풀해서 실행하면 이런 에러가 납니다.
exec /app/server: exec format error
Dockerfile은 단순합니다.
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev
CMD ["node", "server.js"]
빌드는 로컬에서 docker build -t myreg/app:v1 . && docker push로 했고, CI에서는 docker run myreg/app:v1 하면 위 에러. 이미지 manifest를 확인해 보니 arch: arm64로만 되어 있네요. 왜 이런 게 생기고 어떻게 해결하나요?
댓글 3
원인 정확합니다. M2 Mac은 기본적으로 arm64 이미지만 빌드해서 푸시합니다. x86 리눅스에서 pull하면 실행 바이너리 포맷이 달라서
exec format error가 납니다.해결: buildx로 멀티아키 빌드
이러면 manifest list가 만들어져서 pull하는 측 아키텍처에 맞는 이미지를 자동 선택합니다.
CI에서 빌드하는 게 정답
실무에서는 로컬 푸시 자체를 금지하고 CI에서만 이미지 빌드·푸시하는 팀이 많습니다. GitHub Actions에서는
docker/build-push-action+setup-qemu-action조합이 표준입니다.추가 함정: 네이티브 의존성
Node 쪽이라 거의 무관하지만, sharp나 better-sqlite3 같은 네이티브 바인딩은 아키별로 prebuilt가 다릅니다. CI 빌드는 타깃 아키 안에서
npm ci를 돌려야 안전합니다(QEMU로 해결됨).참고로 arm64 전용으로만 배포하는 팀이면 굳이 멀티아키 안 해도 됩니다. AWS Graviton, Ampere GCP 등 최근 클라우드는 arm64 러너가 저렴하니까 아예 CI와 프로덕션을 arm64로 통일하는 게 비용·속도 면에서 유리해요. 저희 팀은 그렇게 이관했습니다.
CI에서 buildx로 amd64/arm64 동시 빌드 + 캐시 적용하니까 해결됐습니다. 빌드 시간이 처음엔 오래 걸려서 걱정했는데 GHA 캐시 먹으니까 2분 대로 떨어지네요. 감사합니다!