Exec format error в Docker: сравните платформу образа и исполняемого файла

На ARM-ноутбуке приложение собирается и запускается, а на сервере x86-64 контейнер заканчивает работу с exec format error. Первое подозрение — образ не для той архитектуры. Но ошибка возникает при запуске конкретного файла: метаданные образа, базовый слой и ваш бинарник могли быть собраны для разных платформ. Замена тега не изменит содержимое файла.

Сравните три места, где могла смениться платформа

На сервере узнайте архитектуру хоста через uname -m (обычно x86_64 соответствует amd64, а aarch64 — arm64). У локального образа посмотрите ОС и архитектуру через docker image inspect. Затем проверьте сам бинарник, который копируете в образ: file ./app на машине сборки показывает тип файла и его архитектуру. Если образ заявляет linux/amd64, а file показывает ARM aarch64, причина найдена.

uname -m
docker image inspect --format '{{.Os}}/{{.Architecture}}' example-app:review
file ./app

Команда docker image inspect описывает выбранный локальный образ; она не проверяет каждую вложенную программу. Для опубликованного образа выполните docker buildx imagetools inspect registry.example.com/team/app:release, подставив свой адрес и тег. Если образ многоплатформенный, раздел Manifests покажет платформы и digest вариантов. На сервере важно проверить именно тот образ или digest, который реально запускается.

Пересоберите файл для целевой платформы

Для Go это удобно сделать внутри многоэтапной сборки. Минимальный main.go печатает платформу запущенного процесса:

package main

import (
    "fmt"
    "runtime"
)

func main() { fmt.Println(runtime.GOOS, runtime.GOARCH) }

Dockerfile ниже запускает компилятор на платформе сборщика, а TARGETOS и TARGETARCH передаёт компилятору как целевую платформу. CGO_ENABLED=0 позволяет этому маленькому примеру не зависеть от C-библиотек целевой системы:

# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETOS
ARG TARGETARCH
WORKDIR /src
COPY main.go .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o app main.go

FROM alpine:3.20
COPY --from=build /src/app /app
ENTRYPOINT ["/app"]

При сборке docker build --platform linux/amd64 -t arch-demo:amd64 . BuildKit задаёт TARGETOS=linux и TARGETARCH=amd64. В результате и финальный образ, и исполняемый Go-файл рассчитаны на linux/amd64. Для ARM-сервера меняется значение --platform на linux/arm64. Это исправляет архитектурное несовпадение без копирования бинарника, собранного на ноутбуке.

Если архитектура совпала, посмотрите на способ запуска

exec format error бывает и у скрипта без строки интерпретатора. Если ENTRYPOINT указывает на entrypoint.sh, проверьте первую строку: например, #!/bin/sh. Убедитесь, что файл действительно является скриптом, имеет нужные права и записан с подходящими окончаниями строк. При ошибке «файл не найден» у существующего бинарника отдельно проверьте динамический загрузчик и системные библиотеки: это другая ветка диагностики, и смена архитектуры её не решит.

После пересборки сопоставьте архитектуру хоста, образа и артефакта и запустите короткую проверку в среде той же платформы. Вывод linux amd64 у примера подтверждает, что запущенный файл собран под нужную ОС и CPU. Для настоящего приложения затем проверьте его зависимости и стартовый сценарий. Если поддерживаете обе платформы, публикуйте оба варианта осознанно и проверяйте каждый.

От единичной ошибки к воспроизводимой сборке

В платном курсе «Docker и Docker Compose» вы напишете Dockerfile для учебного HTTP-сервиса, разберёте многоэтапную сборку, теги и идентичность образов, затем запустите сервис с PostgreSQL через Compose. В итоговом проекте окружение передают другому разработчику и проверяют восстановление данных. Откройте пробный урок «Многоэтапная сборка» и посмотрите программу курса.

Другие причины сбоя запуска собраны в разборах Docker; при медленной пересборке пригодится статья о порядке COPY и кэше.