Three ways to run or build an x86 image on an Apple silicon Mac:
docker run --platform linux/amd64 <image>— run a container as amd64, emulated.docker build --platform linux/amd64 <path>— build an amd64 image regardless of the machine doing the building.platform: linux/amd64under a service incompose.yaml— the Compose equivalent of the flag above, applied tobuild,run, andpullfor that service.
That answers “how.” It doesn’t answer “should.” Before reaching for any of the three, it’s worth thirty seconds to find out whether the image already runs natively on arm64 — because for a lot of the images people search this question about, it does, and forcing amd64 on those just makes them slower for no reason.
Setting it globally: DOCKER_DEFAULT_PLATFORM
Docker’s CLI reference documents an environment variable for this: DOCKER_DEFAULT_PLATFORM, described as the “Default platform for commands that take the --platform flag.” Set it once and every docker run, docker build, and docker pull after that inherits it:
export DOCKER_DEFAULT_PLATFORM=linux/amd64
Treat this one as a scalpel, not something to drop in .zshrc. It converts a per-image decision into a machine-wide one. Set it and forget it, and every multi-arch image you pull afterward — including the ones with a perfectly good arm64 build — gets emulated instead. Nothing in the shell reminds you it’s set.
Check the manifest before you force anything
A Docker image tag doesn’t have to point at one image — it usually points at a manifest list, an index with one entry per architecture. Docker’s multi-platform build documentation describes what happens at pull time: “When you pull the image, the registry returns the manifest list, and Docker automatically selects the correct variant based on the host’s architecture.” The same page gives the concrete case: pull a multi-platform image on a Raspberry Pi and Docker selects linux/arm64; pull the identical tag on an x86-64 laptop and it selects linux/amd64.
Which means the real first question isn’t “how do I force amd64” — it’s “does this tag already have an arm64 entry.” One read-only command answers it without pulling anything:
docker buildx imagetools inspect swaggerapi/swagger-ui | grep Platform
On macOS 26.5.2 (build 25F84), arm64, that returns:
Platform: linux/amd64
Platform: linux/arm/v6
Platform: linux/arm64
Platform: linux/386
Platform: linux/ppc64le
linux/arm64 is already there. Nothing needs forcing for this image.
Running the same command against two other images that regularly come up in “run x86 on Apple silicon” searches:
| Image | Platforms in the manifest (order as returned) |
|---|---|
node:16.17.1 | linux/amd64, linux/arm/v7, linux/arm64/v8 |
node:lts | linux/amd64, unknown/unknown, linux/arm64/v8, unknown/unknown, linux/ppc64le, unknown/unknown |
mysql:latest | linux/amd64, unknown/unknown, linux/arm64/v8, unknown/unknown |
All three already carry an arm64 manifest. node:lts is in that list on purpose: it’s the floating tag that always resolves to whatever the current Node LTS is, so it answers the version of this question people are actually asking today rather than the one a 2022 tutorial asked. The unknown/unknown rows in the mysql and node:lts output aren’t missing platforms — they’re attestation manifests (provenance and SBOM data stored next to the image), and Docker ignores them when it picks a runtime variant. A tag that looks like it’s “missing” arm64 because of stray unknown/unknown lines usually isn’t.
The arm64v8/node trap
A pattern that shows up in older Dockerfile advice: replace FROM node:16.17.1 with FROM arm64v8/node:16.17.1 to “get” an ARM build. Comparing what the two tags actually resolve to shows why that’s a downgrade, not a fix:
$ docker buildx imagetools inspect node:16.17.1 | grep MediaType
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
MediaType: application/vnd.docker.distribution.manifest.v2+json
MediaType: application/vnd.docker.distribution.manifest.v2+json
MediaType: application/vnd.docker.distribution.manifest.v2+json
$ docker buildx imagetools inspect arm64v8/node:16.17.1 | grep MediaType
MediaType: application/vnd.docker.distribution.manifest.v2+json
node:16.17.1 is a manifest list — the first line is the index, the three indented lines under it are the per-architecture manifests it points to. arm64v8/node:16.17.1 returns exactly one line: there’s no index, no architecture choice, just an arm64 image. Hardcoding that tag into a Dockerfile pins the build to arm64 on every machine that reads it. On an amd64 host, that file no longer resolves to a matching build — it either needs QEMU to get through the RUN steps, or fails with exec format error where no emulation is installed. The plain node:16.17.1 tag already picks correctly on both.
When forcing amd64 is actually the right call
The flag has real uses — they just aren’t “any image on Apple silicon.” Reach for --platform linux/amd64 when:
- The
grep Platformoutput genuinely has no arm64 line. Older vendor images and abandoned tags are the usual case. - An arm64 build exists but is broken or behaves differently, and reproducing an amd64-only bug locally is the point.
- The image being built targets an amd64 deployment host:
docker build --platform linux/amd64is about where the artifact will run, not what’s sitting on the desk.
Outside those cases, forcing amd64 is emulation you asked for and didn’t need.
Rosetta vs. QEMU: what Docker’s docs actually commit to
When emulation is the right call, Docker Desktop can hand the translation to Rosetta instead of QEMU. Per Docker’s settings documentation, the option is labeled “Use Rosetta for x86_64/amd64 emulation on Apple Silicon,” it’s under the General tab, and its description reads: “Accelerate x86/AMD64 binary emulation on Apple Silicon. This option is only available if you have selected Apple Virtualization framework as the Virtual Machine Manager.” It’s disabled by default.
That’s the entirety of what the settings page commits to: the option’s name, its location, its off-by-default state, and its dependency on the Apple Virtualization framework. It carries no speedup figure, no percentage, no benchmark — for Rosetta or for QEMU. Any specific number attached to “how much faster Rosetta is” that isn’t sourced to a benchmark you can see the methodology for is not something Docker’s documentation says. If the difference matters for a given workload, running that workload under each setting and timing it settles the question faster than any figure someone else measured on different hardware.
A line in older Compose files that no longer does anything
Compose examples from a few years back tend to open with a top-level version: key, commonly version: '3.8'. Pointing a current Compose install at a file that still has it produces this, with the timestamp and the file’s full path trimmed here for length:
$ docker compose config
...level=warning msg="...docker-compose.yml: the attribute `version` is obsolete,
it will be ignored, please remove it to avoid potential confusion"
On Compose v5.1.2, exit code 0 — the config still resolves, the key is just ignored. It’s safe to delete rather than carry forward into new files.
Where to stop
docker buildx imagetools inspect <image> | grep Platform is the one command worth running before any of the platform-forcing options above. If arm64 shows up in that output, the flag, the compose platform: key, and DOCKER_DEFAULT_PLATFORM all have the same effect on that image: discarding a build that already matches the machine and running a slower one in its place. Save them for the images where the grep output actually comes back without an arm64 line, or for builds that are deliberately targeting an amd64 host somewhere else.