Mahdi BEN RHOUMAexec 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.
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 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
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.
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 build produces a linux/arm64 image, pulling the arm64 variants of multi-platform base images such as node or alpine.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.
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):
docker buildx build --platform linux/amd64 -t my-api:1.4.0 .
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
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"]
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.
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"
}
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.
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 .
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.
Check what you built before you deploy it. For a local image:
docker image inspect my-api:1.4.0 --format '{{.Os}}/{{.Architecture}}'
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
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.
The same exec format error appears in two other situations worth ruling out once the architectures match:
#!/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.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