If your organization uses Docker, the expression “works on my machine” might feel out-of-date to you. But if your organization is also considering moving to newer Macs (circa November 2020), you might find that expression making an unexpected comeback.
We ran into just this situation at Ginkgo Bioworks. We use Docker extensively for developing and running our applications, and normally everything works seamlessly. That is, until we started outfitting our developers with newer MacBook Pros.
We learned that Docker containers can behave differently on different machines–sometimes in complex and confusing ways–and those differences can be significant. In this article, I’d like to share some of what we learned so that others can have a head start working through the issues and sources of confusion they might encounter along the way.
Why Is This Complicated?
Docker images, despite their renowned portability, are sensitive to the chip architecture they are built and run on (Docker calls this the “platform”). This sensitivity isn’t a big deal in a world where everyone uses the same class of chips. Docker images would only need to support a single architecture to make most people happy.
But a couple of years ago, Apple (an admittedly popular company) started rolling out computers with what is colloquially called “Apple Silicon”: M1 or M2 chips that use an ARM architecture instead of the traditional x86 architecture used on Intel chips.
Luckily, Docker images can be crafted to support multiple architectures, and many of the most popular base images are! Ideally, this removes the entire cognitive burden of dealing with different architectures for the average Docker user. For example, if you are working on a machine that uses Apple Silicon, Docker automatically knows to pull and run an image version compatible with that machine.
What if you are on Apple Silicon and the only version of the image available targets x86? Theoretically, Docker Desktop for Mac has you covered. It will automatically emulate an x86 environment if you are trying to use an x86 image on a machine with Apple Silicon.
Protip: You can see the architecture your image is built for with the command docker run <IMAGE> uname -m
. If it echos back “aarch64”, you’ve got an image happily built targeting Apple Silicon. If you see “x86_64” instead, your image targets Intel chips and will run under emulation.
So What’s the Problem?
There are two issues. First, not all base images support all architectures. Second, Docker Desktop emulation isn’t perfect. To quote Docker, “running Intel-based containers on Arm-based machines should be regarded as ‘best effort’ only.” Not only will you get a pesky warning, but you can get bugs too.
Example: “WARNING: The requested image’s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested”
…And that is exactly what we discovered at Ginkgo. Developers who had been issued newer Macs and were working on a Ruby application found that common Ruby commands like bundle install
and rake
would hang forever, making the Docker build and container runtime unusable.
Ultimately, the issue was that the base image we use does not support Apple Silicon. So when a developer pulls this image on a newer Mac, it runs under x86 emulation, which, as we already mentioned, isn’t perfect. In our specific case, we found that an alternative version of malloc we use to reduce ruby’s memory consumption did not agree with its emulated environment. If we removed the library or switched to a base image that supports Apple Silicon, everything would start to work.
Wait, Doesn’t Docker have a “–platform” Flag?
Yup! It sure does. docker run
and docker build
both have a flag that lets you explicitly set the architecture, but they don’t work as one might expect.
For example, running:
docker run --platform=linux/arm64 ...
on Apple Silicon won’t magically make the container work if an image supporting ARM architecture doesn’t exist. Instead, this command tries to create a container from an image already built for ARM architecture. If such an image does not exist locally, Docker will try to pull it. If such an image does not exist remotely either, Docker will fail.
The “build” case is regretfully more confusing. If possible, running:
docker build --platform=linux/arm64 ...
on Apple Silicon will indeed build an image that supports ARM architecture; however, if your base image only supports x86 (as ours does), then Docker will silently build the image against linux/amd64 (aka x86 Intel chips)! This behavior was particularly misleading for us because it made it look like our images should support Apple Silicon and thus run without the Docker Desktop emulator; however, that is the exact opposite of what our images were doing. Remember that pro-tip from before about checking the architecture of your container? It was crucial here to discern the true nature of how our images were being built.
Takeaways
If your organization also relies heavily on Docker and plans a similar move to machines that use ARM chips, hopefully some of the lessons I’ve shared here will be useful. In the end, simply being aware of the common pitfalls and knowing where to look first can save you significant time and make the transition smoother.
(Feature photo by CUTTERSNAP on Unsplash)