Table of Contents
Overview
If you are building Docker images for ARM architectures (like Apple Silicon or Raspberry Pi) on a standard Intel-based Jenkins server, you might have hit a wall that looks exactly like this:
> [3/6] RUN apt-get update && apt-get install -y espeak-ng: 0.188 exec /bin/sh: exec format error

I ran into this recently. My pipeline was working perfectly for weeks using docker buildx, and then suddenly—after adding a single RUN apt-get line—it exploded.
Here is why it happened and the simple “sidecar” fix for your Docker Compose setup.
- The Host: An Intel (AMD64) server running Jenkins via Docker Compose.
- The Target: An ARM64 Docker image (Python 3.12 on Debian Bookworm).
- The Change: I modified the Dockerfile to install a system dependency (
espeak-ng).
This was the most confusing part. My pipeline had been building this ARM image successfully for a while. Why did it fail now?
The answer lies in the difference between moving files and executing code.
- Before: My Dockerfile only used
COPYinstructions. Docker simply moves bytes from the host to the container image. The Intel CPU on the host handles this easily, regardless of the target architecture. - After: I added
RUN apt-get .... To run this, Docker has to spin up the container and execute the/bin/shbinary inside it.
Since the image is ARM64, the /bin/sh binary is written for an ARM processor. When my Intel server tried to run it, the CPU effectively said, “I don’t speak this language,” and threw the exec format error.
Here is my dockerfile:
# Start with an official Python base image.
# Using '-slim' provides a smaller image size.
FROM python:3.12.11-bookworm
# Set the working directory inside the container.
WORKDIR /app
RUN apt-get update && \
apt-get install -y espeak-ng && \
rm -rf /var/lib/apt/lists/*
# Copy the file that lists the dependencies.
# By copying this first, Docker can cache the installed packages layer
# and won't reinstall them unless requirements.txt changes.
COPY requirements.txt .
# Install build dependencies, then install python packages, then remove the build dependencies in a single layer.
# The 'fastuuid' package requires a Rust compiler (cargo) and a C linker (build-base) to build on ARM64.
# Using a virtual package (.build-deps) makes cleanup easy.
RUN pip install --no-cache-dir --upgrade pip -r requirements.txt
# Copy the rest of your application code from your local machine to the container.
COPY . .
# Expose the port the app runs on.
# FastAPI with Uvicorn defaults to 8000.
EXPOSE 8000
# Define the command to run your application.
# This tells uvicorn to run the 'app' instance from the 'main.py' file.
# --host 0.0.0.0 makes the app accessible from outside the container.
# --port 8000 matches the EXPOSE instruction.
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]The part
RUN apt-get update && \
apt-get install -y espeak-ng && \
rm -rf /var/lib/apt/lists/*Was newly added, which caused the error.
The Solution: QEMU Emulation (binfmt)
To fix this, we need to teach the Intel Linux kernel how to understand ARM binaries. We do this using QEMU user-mode emulation and binfmt_misc.
We don’t need to change the Dockerfile. We just need to register the emulators on the Host machine.
Since my Jenkins runs via docker-compose.yml, the best way to handle this is to add a “sidecar” service that runs purely to register these emulators every time the stack starts.
Because Jenkins mounts the Docker socket (/var/run/docker.sock), it uses the Host’s kernel to run builds. Therefore, updating the Host’s kernel benefits Jenkins immediately.
Here is the updated docker-compose.yml:
services:
# 1. The Fix: A service that registers QEMU emulators on the host
qemu-register:
image: tonistiigi/binfmt
privileged: true
command: --install all
entrypoint: /usr/bin/binfmt
# 2. Your existing Jenkins service
jenkins:
build: .
privileged: true
user: root
restart: always
depends_on:
- qemu-register # Ensure qemu runs first
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# ... your other volumesAnd now can build my image successfully again.

Conclusion
In this post, I’ve shown you how to fix the issue of building arm images on x86 host.

I build softwares that solve problems. I also love writing/documenting things I learn/want to learn.