Fix 'exec format error' on AWS Fargate: ARM vs x86 images

Fix 'exec format error' on AWS Fargate: ARM vs x86 images

# docker# awsfargate# amazonecs# troubleshooting
Fix 'exec format error' on AWS Fargate: ARM vs x86 imagesMahdi BEN RHOUMA

exec user process caused: exec format error on Fargate usually means an arm64 image on x86_64 tasks. Rebuild for linux/amd64 or run the task on ARM64.

TL;DR

Fargate stops your container on start with exec user process caused: exec format error when the image's CPU architecture does not match the task's. The classic case: the image was built on an Apple Silicon (M1/M2/M3) Mac, so it is linux/arm64, and the Fargate task runs on the default X86_64. Fix it one of two ways: rebuild the image for linux/amd64 with docker buildx build --platform linux/amd64, or set the task definition's runtimePlatform.cpuArchitecture to ARM64 so the task matches the image.

The error

The container exits immediately, the ECS service keeps restarting it, and the stopped task's log shows one line, reported verbatim on Stack Overflow by a developer deploying a MERN app to ECS/Fargate:

standard_init_linux.go:219: exec user process caused: exec format error
Enter fullscreen mode Exit fullscreen mode

Newer container runtimes word it differently, naming the file they failed to start, for example exec /usr/local/bin/docker-entrypoint.sh: exec format error. Same failure, same cause. The same image usually runs perfectly on the laptop that built it, which is what makes this error confusing: nothing in your code or your Dockerfile is wrong.

Why it happens

A container image is not portable machine code. Every binary inside it, from /bin/sh to node to any native module compiled during npm install, is compiled for one CPU architecture. When a container starts, the Linux kernel on the host is asked to execute the image's entrypoint. If that binary was compiled for arm64 and the host CPU is x86_64, the kernel cannot load it and returns ENOEXEC, which the runtime reports as exec format error. The process never starts, so your application logs nothing.

Two defaults collide here:

  • Docker builds for the machine it runs on. On an Apple Silicon Mac, a plain docker build produces a linux/arm64 image, pulling the arm64 variants of multi-platform base images such as node or alpine.
  • Fargate defaults to x86. In the ECS task definition, runtimePlatform.cpuArchitecture defaults to X86_64. The ECS task definition reference lists X86_64 and ARM64 as the valid values, with ARM64 available for Linux tasks.

The accepted answer on the source question names exactly this mismatch: images built on the M1's ARM architecture cannot run on the x86-64 hosts the task was scheduled on.

Fix 1: rebuild the image for linux/amd64

This keeps the Fargate task on its default architecture. BuildKit's buildx builds for a target platform other than your own, using QEMU emulation when the host cannot run the target natively (Docker multi-platform builds):

  1. Build and tag the image for x86-64:
   docker buildx build --platform linux/amd64 -t my-api:1.4.0 .
Enter fullscreen mode Exit fullscreen mode
  1. Tag and push it to the registry the task pulls from (Amazon ECR here, in eu-west-2):
   docker tag my-api:1.4.0 123456789012.dkr.ecr.eu-west-2.amazonaws.com/my-api:1.4.0
   docker push 123456789012.dkr.ecr.eu-west-2.amazonaws.com/my-api:1.4.0
Enter fullscreen mode Exit fullscreen mode
  1. Register a new task definition revision pointing at the new tag and update the service.

If you prefer the platform to live in the Dockerfile itself, pin it on the base image. Another answer on the question does exactly that, and it is what makes an accidental local docker build safe:

FROM --platform=linux/amd64 node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

Expect the emulated build to be slower than a native one. The Docker documentation warns that QEMU emulation "can be much slower than native builds", especially for compilation-heavy steps such as native npm modules. If npm ci compiles C++ addons, that is where the time goes.

Fix 2: run the Fargate task on ARM64

If your image is arm64 and you would rather keep it that way, make the task match it. In the task definition, add or change the runtimePlatform block (the other required fields, such as containerDefinitions and "networkMode": "awsvpc", are omitted here and stay as they are):

{
  "family": "my-api",
  "requiresCompatibilities": ["FARGATE"],
  "runtimePlatform": {
    "operatingSystemFamily": "LINUX",
    "cpuArchitecture": "ARM64"
  },
  "cpu": "512",
  "memory": "1024"
}
Enter fullscreen mode Exit fullscreen mode

Every task definition used by one service must share the same cpuArchitecture value, so change it for the whole service, not for a single revision you test by hand. This only works if every base image and every native dependency in the image exists for arm64; most official images (node, alpine, postgres) publish arm64 variants, but a private base image or a vendor binary may not.

Fix 3: publish one image for both architectures

When the same image must run on arm64 laptops and x86 servers, build a multi-platform image. The registry then stores one manifest list, and each host pulls the variant for its own CPU:

docker buildx build --platform linux/amd64,linux/arm64 -t 123456789012.dkr.ecr.eu-west-2.amazonaws.com/my-api:1.4.0 --push .
Enter fullscreen mode Exit fullscreen mode

Push straight to the registry: the documentation notes that images built with the docker-container driver are not loaded into the local image store automatically, so --push is the reliable way to get every variant where the task will pull it. If the builder cannot emulate a platform, register QEMU handlers first with docker run --privileged --rm tonistiigi/binfmt --install all.

Verify the fix

Check what you built before you deploy it. For a local image:

docker image inspect my-api:1.4.0 --format '{{.Os}}/{{.Architecture}}'
Enter fullscreen mode Exit fullscreen mode

It must print linux/amd64 for a default Fargate task, or linux/arm64 if you chose Fix 2. For an image already pushed, list every platform its manifest carries:

docker buildx imagetools inspect 123456789012.dkr.ecr.eu-west-2.amazonaws.com/my-api:1.4.0
Enter fullscreen mode Exit fullscreen mode

Then force a new deployment of the service and watch the task reach RUNNING. If the task still stops, open the stopped task's reason in the ECS console: a different message there means you have moved on to a different problem, such as a missing environment variable or a failing health check.

When the architecture is not the problem

The same exec format error appears in two other situations worth ruling out once the architectures match:

  • A shell script entrypoint with no shebang line. Without #!/bin/sh as its first line, the kernel does not know how to execute the file and fails the same way. Add the shebang and keep the file executable.
  • A binary copied in from the build host. A COPY of a tool you compiled on your Mac brings a macOS or arm64 binary into a Linux x86 image. Build such tools inside the image, in a build stage, instead.

The trap is not specific to Fargate either. The same build-machine-versus-runtime mismatch is behind Prisma's "query engine library for current platform" error, where the client is generated for one platform and run on another. For the Compose side of a containerised dev setup, see the Docker development environment tutorial. If you deploy a Next.js app in a container, the runtime also has to meet the framework's floor: see the minimum Node.js version for Next.js 15 before you pick a base image, and the production deployment guide for Next.js and Supabase for the rest of the checklist.

Build for the platform you run on, check the image before it ships, and this error does not come back.


Originally published at https://www.iloveblogs.blog