Docker no Mac: erro de plataforma amd64 e arm64

Peguei esse erro pela primeira vez num MacBook M1, buildando a imagem de uma aplicação PHP com Laravel. O build terminava, o docker run subia, e no meio do caminho aparecia isto:

WARNING: The requested image's platform (linux/amd64) does not match the detected
host platform (linux/arm64/v8) and no specific platform was requested
exec format error

Minha primeira suspeita foi o Dockerfile. Fiquei de alguns minutos a quase uma hora mexendo em imagem base, ordem de camadas e entrypoint, procurando o que eu tinha escrito errado. Não tinha. O Dockerfile estava certo, e o problema nem passava perto dele.

Um colega bateu no mesmo erro na máquina dele pouco depois, o que já descartou configuração isolada da minha máquina.

O que a mensagem está dizendo

A mensagem entrega duas informações, e a maior parte da confusão vem de ler só metade dela:

  • The requested image's platform (linux/amd64): a imagem que você está usando foi construída para Intel/AMD.
  • the detected host platform (linux/arm64/v8): a sua máquina é Apple Silicon.

Ou seja, não é o Docker reclamando do seu código. É o Docker avisando que a imagem foi feita para um processador diferente do seu.

Vale prestar atenção na ordem, porque a mesma mensagem aparece invertida e aí significa outra coisa. Se o seu host for linux/amd64 e a imagem linux/arm64, o caso é o oposto: um Mac Intel ou um runner de CI tentando rodar uma imagem construída em Apple Silicon. Se for o seu caso, o cenário está detalhado em imagem arm64 num host amd64.

O exec format error que costuma vir junto é o que mais engana. Ele tem cara de entrypoint quebrado, de shebang errado, de binário corrompido. Na prática é o kernel dizendo que tentou executar um binário compilado para outro conjunto de instruções e não soube o que fazer com aquilo. É o mesmo tipo de sintoma enganoso de erros de permissão nos logs do Laravel, onde a mensagem aponta para um lugar e a causa está em outro.

Por que o Dockerfile é o suspeito errado

Perdi tempo no Dockerfile por três motivos que provavelmente vão pegar você também:

  1. O erro aparece no docker run, não no docker build. Dá a impressão de que a imagem foi construída errada.
  2. A palavra exec puxa a atenção para CMD e ENTRYPOINT.
  3. O build tinha acabado de passar sem reclamar. Se o build passou, o Dockerfile está bom, certo? Nem sempre.

O detalhe que resolve o diagnóstico em dez segundos é comparar arquitetura da imagem com arquitetura do host:

uname -m
docker image inspect minha-app --format '{{.Os}}/{{.Architecture}}'

Se o primeiro devolver arm64 e o segundo linux/amd64, achou.

A saída que usei na época, e por que ela é ruim

Buildei a imagem numa estação Linux. Funcionou.

Foi, honestamente, o que eu sabia fazer naquele momento. Não era elegante nem prático: dependia de outra máquina estar ligada e acessível, quebrava o ciclo de editar e testar local, e não resolvia nada para o resto do time. Cada pessoa com Mac ia esbarrar no mesmo muro.

Funcionou como gambiarra provisória. Como processo, não parava em pé.

O que resolve sem sair do Mac

A flag --platform diz explicitamente qual arquitetura você quer, em vez de deixar o Docker adivinhar:

docker build --platform linux/amd64 -t minha-app .
docker run --platform linux/amd64 minha-app

Isso resolve o erro. Só que cobra um preço, e é aqui que quase todo post sobre esse assunto para de falar.

O preço da emulação, medido

Buildar para uma arquitetura diferente da sua significa emular, e emulação custa caro. Em vez de chutar um número, medi.

Ambiente do teste: host x86_64, Docker 28.0.1, buildx v0.21.1. A mesma imagem construída duas vezes, nativa e emulada, na mesma máquina, com --no-cache e com as imagens base já baixadas para tirar o download da conta.

O Dockerfile compila extensões PHP, que é trabalho de CPU pesado e representativo de imagem de aplicação real:

FROM php:8.3-fpm
RUN apt-get update && apt-get install -y --no-install-recommends \
      libzip-dev zlib1g-dev \
 && docker-php-ext-install pdo_mysql zip opcache \
 && rm -rf /var/lib/apt/lists/*

Rodei duas vezes cada um, para não depender de uma medição solitária:

BuildRodada 1Rodada 2
Nativo (linux/amd64 no host x86_64)49 s44 s
Emulado (linux/arm64 no host x86_64)1034 s1055 s

Cerca de 22 vezes mais lento. Um build de menos de um minuto virou dezessete.

Dois avisos sobre esse número. Primeiro: meu teste emula arm64 sobre amd64, que é o espelho do caso do M1. A direção muda, o custo de emular arquitetura estrangeira não desaparece. Segundo: o número depende da máquina e do que o Dockerfile faz. Se o seu build só copia arquivos, a diferença será bem menor. Se compila, espere algo dessa ordem.

A documentação do Docker diz a mesma coisa com todas as letras: emulação com QEMU pode ser muito mais lenta que build nativo, especialmente em tarefas pesadas de CPU como compilação e compressão. Reproduza o teste na sua máquina antes de acreditar em mim ou em qualquer outro post.

Rosetta resolve mesmo?

Aqui mora uma confusão que eu já vi repetida em vários lugares, inclusive numa versão anterior deste post.

O Rosetta 2 da Apple traduz aplicativos macOS compilados para Intel. Ele não é, por si só, o que faz um container Linux amd64 rodar no seu Mac com Apple Silicon. Container Linux roda dentro da VM do Docker Desktop, que é outro mundo.

O que existe é uma configuração específica do Docker Desktop: Use Rosetta for x86_64/amd64 emulation on Apple Silicon, em Settings, General. Ela vem desativada por padrão e depende de você estar usando o Apple Virtualization framework como Virtual Machine Manager. Quando ligada, ela acelera a emulação de binários amd64 dentro dos containers, o que ajuda bastante, mas continua sendo emulação.

E não, uname -m não passa a devolver x86_64 no seu macOS por causa do Rosetta. No terminal do Mac ele devolve arm64. Dentro de um container amd64 ele devolve x86_64, que é uma coisa completamente diferente e não prova nada sobre o host.

A correção que ficou: build no CI

O que encerrou o problema de vez não foi flag nem configuração de Docker Desktop. Foi tirar o build da máquina das pessoas.

Padronizamos o build no CI, com runner amd64, gerando imagem só para amd64, porque produção era amd64. Depois disso o assunto morreu.

A lógica é simples: o artefato que vai para produção passou a ser construído num ambiente igual ao de produção, sempre o mesmo, independente de quem estava com Mac, com Linux ou com Windows. Deixou de existir “funciona na minha máquina” para arquitetura de processador.

Repare que a decisão de gerar só amd64 foi consequência de uma pergunta simples: para onde essa imagem vai? Se o alvo é conhecido e único, construir para várias arquiteturas é trabalho sem retorno.

Minha leitura

Build multi-arch com buildx é a resposta certa para quem publica imagem pública, que vai rodar em máquina de terceiros que você não controla. Se você mantém uma imagem base ou uma ferramenta open source, faça multi-arch.

Para um time que faz deploy da própria aplicação num alvo conhecido, é complexidade sem retorno. Fixe a arquitetura de produção, construa no CI e siga a vida.

Ambiente de build que depende do laptop de quem está buildando vai falhar mais cedo ou tarde. A arquitetura do processador foi só a forma como isso apareceu dessa vez. Passar o build para o CI resolveu a causa raiz, não só esse erro específico.

Compartilhe / Share
Fernando Müller Junior
Fernando Müller Junior

SRE Manager na Appmax. 16 anos de infraestrutura, de datacenter a cloud. Escrevo sobre o que opero em produção.

Artigos: 31

Receba as notícias por email / Receive news by email

Insira seu endereço de e-mail abaixo e assine nossa newsletter / Enter your email address below and subscribe to our newsletter

Um comentário

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *