Ruby gem management — bundler as the project-level dep manager, the vendor/bundle pattern, rubocop / standard / solargraph installation choices, and why mise's gem: backend is limited compared to aqua for Ruby CLIs that exist. Use when managing gems for a project.
Ruby's package manager is gem, layered with bundler for project-level dep management. mise's job is to pin Ruby; bundler's job is to manage this project's gems on top.
mise
└─> ruby 3.3.6 (installed to ~/.local/share/mise/installs/ruby/3.3.6)
└─> gem (ships with ruby)
└─> bundler (installed into mise's gem dir, or per-project via corepack-like)
└─> project gems (in vendor/bundle/ if you follow the rules)
Each mise-installed Ruby has its own gem command and its own default gem install dir. Switching Ruby versions gives you a fresh gem set. Bundler on top scopes gems to the project.
After , bundler isn't there yet:
mise install [email protected]gem install bundler
This installs bundler into ~/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/. The binary gets shimmed onto PATH.
Alternatively, commit a Gemfile with a gemspec or bundler version and bundle install will self-bootstrap from the stdlib's rubygems.
Default behavior: bundle install writes to the mise install tree's global gem dir. Every project's gems pile up there.
Better: scope bundler to the project:
bundle config set --local path 'vendor/bundle'
bundle install
Now gems live in ./vendor/bundle/, which:
rm -rf vendor/bundle.vendor/bundle/ to .gitignore).Gemfile.lock.Wire in mise.toml:
[tools]
ruby = "3.3.6"
[env]
BUNDLE_PATH = "vendor/bundle"
BUNDLE_BIN = "vendor/bundle/bin"
_.path = ["vendor/bundle/bin"]
[tasks.install]
run = "bundle install"
sources = ["Gemfile", "Gemfile.lock", "*.gemspec"]
[tasks.test]
depends = ["install"]
run = "bundle exec rspec"
With BUNDLE_BIN = "vendor/bundle/bin" and that dir on PATH, you can drop bundle exec in most cases and call rspec / rubocop / rails directly.
bundle exec vs binstubs vs PATHThree ways to run gem-provided binaries:
bundle exec rspecExplicit, always works. Slow (re-parses Gemfile every time).
bin/bundle binstubs rspec-core
bin/rspec
Writes bin/rspec as a small shim that activates the bundle and execs the real rspec. Fast, explicit, committed to git. The Rails convention.
BUNDLE_BIN)[env]
BUNDLE_BIN = "vendor/bundle/bin"
_.path = ["vendor/bundle/bin"]
rspec # just works
Fast, implicit. Doesn't work for every gem (some don't register bin stubs). Best for dev workflow; binstubs are better for committed bin/dev-style scripts.
Use binstubs for Rails. Use BUNDLE_BIN for non-Rails.
The most common gem-installed CLIs. Three ways to get them:
# Gemfile
group :development do
gem "rubocop", "~> 1.68"
gem "solargraph"
end
[tasks.lint]
run = "bundle exec rubocop"
Pro: same version for every contributor and CI. Con: loads bundler every invocation unless you binstub.
[tools]
ruby = "3.3"
"gem:rubocop" = "latest"
"gem:solargraph" = "latest"
Pro: global tool, shimmed by mise. Con: uses the ruby_version-specific gem dir, so changing Ruby means reinstall.
Most Ruby gem CLIs don't ship GitHub-release binaries — they ship as gems only. So aqua: usually isn't an option for Ruby tools.
Rule: project-scoped via Gemfile for linters and formatters (everyone gets the same version). Global via gem: backend only for personal-preference tools (pry, byebug).
gem: backend — limited, but usefulmise's gem: backend installs rubygems as mise-managed tools:
[tools]
ruby = "3.3"
"gem:rubocop" = "1.68"
"gem:fastlane" = "latest"
Limitations:
Use for: global gems that are inherently tied to Ruby, when a Gemfile isn't appropriate (e.g. a dotfiles-level fastlane install).
For most project tools, prefer a Gemfile + binstubs.
Gemfile.lock for applications (Rails apps, services). Don't commit for libraries (gems) — let consumers resolve their own deps.bundle install --frozen in CI — fails if the lockfile would change.bundle outdated to see what could be updated. Do deliberate upgrades, not blind bundle update.bundle update <gem> to bump one gem. Avoid bundle update alone (bumps everything).[tools]
ruby = "3.3.6"
node = "24"
[hooks]
enter = "corepack enable 2>/dev/null || true"
[env]
BUNDLE_PATH = "vendor/bundle"
RAILS_ENV = { required = "development or production" }
[tasks.install]
run = [
"bundle install",
"pnpm install --frozen-lockfile"
]
[tasks.dev]
depends = ["install"]
run = "bin/dev"
[tasks.test]
depends = ["install"]
run = "bin/rails test"
[tasks."db:migrate"]
depends = ["install"]
run = "bin/rails db:migrate"
gem install <anything> without --user-install as a workaround for permission errors — indicates you're using system Ruby. Fix the root cause (use mise).vendor/bundle/ — no. Lock + reinstall.Gemfile.lock "to resolve conflicts" — no. Rebase / resolve properly.sudo gem install anywhere, for any reason.mise-lang-ruby-overview — Ruby version resolution and build deps.mise-migrate-from-rbenv — moving off rbenv.mise-tasks-toml — sources / outputs for incremental tasks.bundler.io.