heroku / heroku-buildpack-ruby

Heroku's buildpack for Ruby applications.
MIT License
787 stars 1.87k forks source link

Support ARM64 when using Heroku-24 Docker images #1457

Open edmorley opened 4 months ago

edmorley commented 4 months ago

Heroku itself currently runs on AMD64 CPUs, however, some users use our buildpacks locally on machines with ARM64 CPUs (such as M1/M2/M3 MacBooks) with the Heroku base images published to Docker Hub.

As such there have been requests to support the ARM64 architecture, e.g.: https://github.com/heroku/base-images/issues/194

Starting with Heroku-24, the base images published to Docker Hub are now multi-architecture (AMD64 + ARM64), and our preview Cloud Native Buildpacks support ARM64 when using Heroku-24.

However, until CNBs leave preview there will still be users using our classic buildpacks with our base images from Docker Hub, so it would be ideal if we could add ARM64 support to our classic buildpacks too. This will not only make the images faster to run locally, but also avoid breaking local development workflows if users update to Heroku-24 and miss the mention in the stack upgrade notes about using --platform linux/amd64 to force the architecture back to AMD64.

For example, the buildpack should support:

  1. git clone https://github.com/heroku/ruby-getting-started && cd ruby-getting-started
  2. Add a Dockerfile with the below contents.
  3. docker build --tag arm-test --platform linux/arm64 .
  4. docker run --rm -e PORT=5001 -p 5001:5001 arm-test
  5. curl localhost:5001
FROM heroku/heroku:24-build as build
ENV STACK=heroku-24
COPY --chown=heroku . /app
WORKDIR /app
# This is after the COPY line so buildpack updates are picked up.
RUN mkdir -p /tmp/buildpack /tmp/cache /tmp/env \
  && curl https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/ruby.tgz \
    | tar -xz -C /tmp/buildpack
RUN /tmp/buildpack/bin/compile /app /tmp/cache /tmp/env

FROM heroku/heroku:24
COPY --from=build --chown=heroku /app /app
ENV HOME=/app
WORKDIR /app
CMD ["bash", "-c", "for f in .profile.d/*; do source \"${f}\"; done && bundle exec puma -C config/puma.rb"]

cc @schneems

edmorley commented 4 months ago

As of #1439 and #1446 the Ruby install parts of the classic Ruby buildpack supports ARM64 on Heroku-24.

However, when I attempt the build of the getting started Rails app, I get an error from the Node.js install performed by this buildpack (that attempts to install Node.js if there wasn't one already installed by an earlier buildpack):

 > [build 5/5] RUN /tmp/buildpack/bin/compile /app /tmp/cache /tmp/env:                                                     
2.456 -----> Installing bundler 2.5.6                                                                                       
2.456 -----> Removing BUNDLED WITH version in the Gemfile.lock                                                              
3.314 -----> Compiling Ruby/Rails                                                                                           
5.709 -----> Using Ruby version: ruby-3.2.4                                                                                 
5.803 -----> Installing dependencies using bundler 2.5.6
...
48.64 ###### WARNING:
48.64 
48.64        Installing a default version (20.9.0) of Node.js.
48.64        This version is not pinned and can change over time, causing unexpected failures.
48.64        
48.64        Heroku recommends placing the `heroku/nodejs` buildpack in front of
48.64        `heroku/ruby` to install a specific version of node:
48.64        
48.64        https://devcenter.heroku.com/articles/ruby-support#node-js-support
48.64 
48.64 -----> Installing node-v20.9.0-linux-x64
64.44 -----> Detecting rake tasks
65.20 -----> Preparing app for Rails asset pipeline
65.20        Running: rake assets:precompile
65.75        rake aborted!
65.75        ExecJS::RuntimeError: rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2 (ExecJS::RuntimeError)
65.75         
65.75        (execjs):1
...

This is because the buildpack hardcodes the x64 variant of the Node.js download here: https://github.com/heroku/heroku-buildpack-ruby/blob/1c691ff13e282b2aa83b926f89a78be2372870ea/lib/language_pack/helpers/nodebin.rb#L10

Users are also not able to use the Node.js buildpack to install Node.js instead, due to: https://github.com/heroku/heroku-buildpack-nodejs/issues/1268

Separate to the above, anyone who is using JRuby will also presumably be blocked by: https://github.com/heroku/heroku-buildpack-jvm-common/issues/302 ...due to the Ruby buildpack using jvm-common here: https://github.com/heroku/heroku-buildpack-ruby/blob/1c691ff13e282b2aa83b926f89a78be2372870ea/bin/compile#L32

schneems commented 3 months ago

Correct, the TODO here would be updating our node install logic when the node buildpack isn't being used.

We would need to update https://github.com/heroku/heroku-buildpack-ruby/blob/cf25ace0e09675e744603962e12451943ce979be/lib/language_pack/helpers/nodebin.rb#L10.

schneems commented 3 months ago

Here's updated URLs

https://github.com/heroku/buildpacks-nodejs/blob/e1d4420462be7526ec66a85da49445ac7de2b1c9/buildpacks/nodejs-engine/inventory.toml#L365-L377

mlarraz commented 2 weeks ago

FYI I have a PR doing exactly this. I've tested it against one of our internal apps and it builds correctly on both x64 and ARM.