From Apple Silicon to Heroku Docker Registry Without Swearing
Build Intel64-compatible Docker images from Mac M1
Docker Desktop (natively) and Heroku CLI (with Rosetta) work nicely on the Apple M1 processor, however, a locally built Docker image will not successfully deploy onto the Heroku Docker Registry.
In a previous article Deploy to Heroku From a MacBook M1: Heroku CLI or GitHubActions, I described how it is possible (and very convenient) to build a Docker image and deploy it to Heroku using GitHub Actions. Here I am going to look at how to, in a Mac development environment, build and release an image that is compatible with the Heroku Docker Registry runtime.
Image by author
What is happening?
Building and deploying Docker containers on Heroku Docker Registry is pretty simple. First, satisfy the prerequisites :
Login into Heroku (
heroku login
) and Heroku Docker Registry (heroku:container login
)Place the
Dockerfile
in the root folder
Build and release the application with a few simple steps:
# create Heroku app
% heroku create myapp
# set ConfigVar (if env variables are necessary)
% heroku config:set PARAM="value"
# build and push Docker image
% heroku container:push web -a myapp
# release
% heroku container:release web -a myapp
That should all be enough: the “push” and “release” phases of the build complete successfully, however when accessing the application (myapp.herokuapp.com
) the “Error: Exec format error” appears.
Whaaat daa..
Screenshot by author
Let’s look into the image created by the Heroku command above
% docker inspect registry.heroku.com/myapp/web
[
{
"Id": "sha256:38005888ba52553018768ee5f4997d4d599fbeafed88c166f2c0a71fc6b49abc",
"RepoTags": [
"registry.heroku.com/myapp/web:latest"
],
...
},
"Architecture": "arm64",
"Os": "linux",
Docker is by design multi-platform and can run on different architectures, however, the images must match the platform they will be run on. Which is not our case.
Option A: buildx
Buildx is a Docker plugin that allows, amongst other features, to build multi-platform images.
We are developing on the Mac ARM architecture but we want to create a x86-compatible image. The solution is NOT to use the heroku:container push
command but rather to build the image locally with Docker buildx
.
docker buildx build --platform linux/amd64 -t myapp .
# tag image to match Heroku naming conventions
docker tag myapp registry.heroku.com/myapp/web
# push
docker push registry.heroku.com/myapp/web
Once pushed the image can be released
heroku container:release web -a myapp
Option B: set DOCKER_DEFAULT_PLATFORM
The DOCKER_DEFAULT_PLATFORM
environment variable permits to set the default platform for the commands that take the --platform
flag.
export DOCKER_DEFAULT_PLATFORM=linux/amd64
You can now go ahead and use the standard Heroku CLI commands (since Docker underneath will run with the platform indicated by the setting above).
# build and push Docker image
% heroku container:push web -a myapp
# release
% heroku container:release web -a myapp
Conclusion
The two solutions presented are both valid: my personal choice is to “delegate” the build to a CI/CD tool (like GitHub Actions), or to settle for the first option (buildx
) which keeps the flexibility of switching easily between platforms (it may not the case when using an environment variable).
Check out the GitHub repository showing various options to deploy on Heroku.
Happy coding ✌️