Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions .github/workflows/check_changelog.yml

This file was deleted.

56 changes: 0 additions & 56 deletions .github/workflows/ci.yml

This file was deleted.

43 changes: 0 additions & 43 deletions .github/workflows/document_ruby_version.yml

This file was deleted.

31 changes: 0 additions & 31 deletions .github/workflows/hatchet_app_cleaner.yml

This file was deleted.

27 changes: 0 additions & 27 deletions .github/workflows/prepare-release.yml

This file was deleted.

98 changes: 59 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,79 @@
# Heroku Buildpack for Ruby
# Heroku Buildpack for Ruby (next_rails dual-boot fork)

![ruby](https://raw.githubusercontent.com/heroku/buildpacks/refs/heads/main/assets/images/buildpack-banner-ruby.png)

This is a [Heroku Buildpack](http://devcenter.heroku.com/articles/buildpacks) for Ruby, Rack, and Rails apps. It uses [Bundler](https://bundler.io) for dependency management.
This is a fork of [Heroku's official Ruby buildpack](https://github.com/heroku/heroku-buildpack-ruby). The **only** thing it adds is dual-boot support through the `BUNDLE_GEMFILE` environment variable. For all standard Ruby, Rack, and Rails buildpack behavior, and for full documentation, use the official buildpack and the [Heroku Ruby Support](https://devcenter.heroku.com/articles/ruby-support) docs.

This buildpack requires 64-bit Linux.
> ## ⚠️ Read this first: changing `BUNDLE_GEMFILE` requires a rebuild, not just a config change
>
> Heroku triggers a **re-release but not a rebuild** when you change a config var. The build is what installs gems, so flipping `BUNDLE_GEMFILE` on its own never installs the other Rails version's gems.
>
> Example: an app built with Rails 6.1 only has 6.1 gems in its slug. If you then change `BUNDLE_GEMFILE` to `Gemfile.next` (Rails 7.0):
>
> - Heroku runs a new release with the changed env but does **not** rebuild the slug.
> - The slug still has only the previous version's gems. In some cases the re-release may fail, and Heroku might revert the config var to its previous value. If that happens, the env change ends up **not actually applied at runtime**, so Heroku reports the var changed while the app keeps running the previous version. Confusing, but expected.
>
> **To switch versions: change `BUNDLE_GEMFILE` and then trigger a deploy (e.g. push a commit) so the correct gems are installed at build time.** Do not rely on flipping the config var by itself.

## Usage
## What this fork is for

### Ruby
It lets a single app switch between its current Gemfile and a [`next_rails`](https://github.com/fastruby/next_rails)-style alternative (e.g. `Gemfile.next`) at deploy time. `next_rails` is maintained by [FastRuby.io](https://www.fastruby.io).

Example Usage:
- `BUNDLE_GEMFILE` **unset** → behaves exactly like the official `heroku/ruby` buildpack (uses `Gemfile` / `Gemfile.lock`). Drop-in replacement.
- `BUNDLE_GEMFILE=Gemfile.next` → builds and runs against `Gemfile.next` / `Gemfile.next.lock`.

$ ls
Gemfile Gemfile.lock
## Usage

$ heroku create --buildpack heroku/ruby
1. Confirm the app boots locally on the next version:

$ git push heroku main
...
-----> Heroku receiving push
-----> Fetching custom buildpack
-----> Ruby app detected
-----> Installing dependencies using Bundler version 1.1.rc
Running: bundle install --without development:test --path vendor/bundle --deployment
Fetching gem metadata from http://rubygems.org/..
Installing rack (1.3.5)
Using bundler (1.1.rc)
Your bundle is complete! It was installed into ./vendor/bundle
Cleaning up the bundler cache.
-----> Discovering process types
Procfile declares types -> (none)
Default types for Ruby -> console, rake
```sh
BUNDLE_GEMFILE=Gemfile.next bundle exec rails -v
```

The buildpack will detect your app as Ruby if it has a `Gemfile` and `Gemfile.lock` files in the root directory. It will then proceed to run `bundle install` after setting up the appropriate environment for [ruby](http://ruby-lang.org) and [Bundler](https://bundler.io).
2. Point the app at this fork (pin to a branch or tag):

## Documentation
```sh
heroku buildpacks:set https://github.com/fastruby/heroku-buildpack-ruby#use_gemfile_next_v359 -a <app>

@JuanVqz JuanVqz Jun 9, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arielj are we going to merge this PR or keep it as the previous version? if so, we need to update this to main or probably just the URL will pickup the default branch.

```

For more information about using Ruby and buildpacks on Heroku, see these Dev Center articles:
3. Set the config var:

- [Heroku Ruby Support](https://devcenter.heroku.com/articles/ruby-support)
- [Getting Started with Ruby on Heroku](https://devcenter.heroku.com/articles/getting-started-with-ruby)
- [Getting Started with Rails 7 on Heroku](https://devcenter.heroku.com/articles/getting-started-with-rails7)
- [Buildpacks](https://devcenter.heroku.com/articles/buildpacks)
- [Buildpack API](https://devcenter.heroku.com/articles/buildpack-api)
```sh
heroku config:set BUNDLE_GEMFILE=Gemfile.next -a <app>
```

## Hacking
4. Deploy. This is the rebuild that installs the next gems (see the warning above):

To use this buildpack, fork it on Github. Push up changes to your fork, then create a test app with `--buildpack <your-github-url>` and push to it.
```sh
git push heroku <branch>:main
```

### Testing
5. Verify:

```sh
$ bundle exec hatchet install
```
```sh
heroku run "bundle exec rails -v" -a <app>
```

To go back to the current version, unset the var and redeploy:

```sh
$ bundle exec rake spec
heroku config:unset BUNDLE_GEMFILE -a <app>
# then deploy again
```

## Alternative: a next-only buildpack

If you want an app that always runs the next version without managing `BUNDLE_GEMFILE`, use our sibling fork [`fastruby/heroku-buildpack-ruby-gemfile-next`](https://github.com/fastruby/heroku-buildpack-ruby-gemfile-next). It always uses `Gemfile.next` / `Gemfile.next.lock` and needs no `BUNDLE_GEMFILE`. To return to the current version, switch the app's buildpack back to the official `heroku/ruby`.

## Documentation

This fork only changes which Gemfile drives the build. For everything else, see:

- [Official Heroku Ruby buildpack](https://github.com/heroku/heroku-buildpack-ruby)
- [Heroku Ruby Support](https://devcenter.heroku.com/articles/ruby-support)
- [Buildpacks](https://devcenter.heroku.com/articles/buildpacks)
- [next_rails](https://github.com/fastruby/next_rails)

## License

See [LICENSE](LICENSE). Originally created by Heroku, Inc.
6 changes: 5 additions & 1 deletion bin/support/ruby_compile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
begin
app_path = Pathname(ARGV[0])
cache_path = Pathname(ARGV[1])
gemfile_lock = LanguagePack.gemfile_lock(app_path: app_path)
Dir.chdir(app_path)

# Load user config vars from the env dir before we touch the Gemfile so that
# BUNDLE_GEMFILE (set as a Heroku config var) can steer which lockfile we
# read. Without this, gemfile_lock would always read Gemfile.lock regardless
# of the user's BUNDLE_GEMFILE setting.
LanguagePack::ShellHelpers.initialize_env(ARGV[2])
gemfile_lock = LanguagePack.gemfile_lock(app_path: app_path)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heroku passes user config vars to buildpacks via a directory of files (one file per var), not as actual ENV entries. The path to that dir is ARGV[2] in ruby_compile.rb.
Until LanguagePack::ShellHelpers.initialize_env(ARGV[2]) runs, that data isn't loaded anywhere.

Sequence before our fix:

gemfile_lock = LanguagePack.gemfile_lock(...)   # reads ENV["BUNDLE_GEMFILE"] → ""
Dir.chdir(app_path)
LanguagePack::ShellHelpers.initialize_env(...)  # now user_env_hash["BUNDLE_GEMFILE"] = "Gemfile.next"

LanguagePack.gemfile_name (which gemfile_lock calls via lockfile_name) reads:

  ENV["BUNDLE_GEMFILE"]                            # empty, Heroku doesn't set it in ENV
  || LanguagePack::ShellHelpers.user_env_hash[..]  # empty too, initialize_env hasn't run yet
  || "Gemfile"                                     # falls through to default

So it picked Gemfile.lock. The bundler/Ruby version detection then used the wrong lockfile, and build_bundler later built against Gemfile. Meanwhile bundle list (called with user_env: true) read user_env_hash["BUNDLE_GEMFILE"] = Gemfile.next and tried to verify gems from Gemfile.next.lock which had never been installed. That's the exact mismatch the failed build exposed.

After moving the line below initialize_env, user_env_hash is populated first, gemfile_name resolves to Gemfile.next, and every later phase agrees on the same lockfile.

LanguagePack.call(
app_path: app_path,
cache_path: cache_path,
Expand Down
21 changes: 19 additions & 2 deletions lib/language_pack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,31 @@ module LanguagePack
module Helpers
end

# Name of the Gemfile this buildpack should use. Honors the BUNDLE_GEMFILE
# env var (typically set via a Heroku config var) so a user can flip between
# the current and next Gemfile without changing buildpack URLs. Falls back
# to "Gemfile", the same default as the stock heroku/ruby buildpack, making
# this a drop-in replacement when BUNDLE_GEMFILE is not set.
def self.gemfile_name
raw = ENV["BUNDLE_GEMFILE"].to_s
if raw.empty? && defined?(LanguagePack::ShellHelpers)
raw = LanguagePack::ShellHelpers.user_env_hash["BUNDLE_GEMFILE"].to_s
end
raw.empty? ? "Gemfile" : File.basename(raw)
end

def self.lockfile_name
"#{gemfile_name}.lock"
end

def self.gemfile_lock(app_path:)
path = app_path.join("Gemfile.lock")
path = app_path.join(lockfile_name)
if path.exist?
LanguagePack::Helpers::GemfileLock.new(
contents: path.read
)
else
raise BuildpackError.new("Gemfile.lock required. Please check it in.")
raise BuildpackError.new("#{lockfile_name} required. Please check it in.")
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/language_pack/helpers/bundler_wrapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class LanguagePack::Helpers::BundlerWrapper
def initialize(
bundler_path:,
bundler_version:,
gemfile_path: Pathname.new("./Gemfile"),
gemfile_path: Pathname.new("./#{LanguagePack.gemfile_name}"),
report: HerokuBuildReport::GLOBAL
)
@report = report
Expand Down
Loading