skip to content

With Bundler 4, why does `bundle install --without test --path vendor/bundle` fail, and how are those settings expressed with `bundle config`?

level: seniorimportance: should knowfreq 45%

answer

  1. remembered flags are gone
  2. InvalidOption: use bundle config set
  3. --local writes .bundle/config
  4. local, then ENV, then global
  5. BUNDLE_WITHOUT, BUNDLE_PATH

basics

~20 s

Bundler 4 removed install flags that used to be silently remembered; --without and --path raise InvalidOption. Set them with bundle config set --local without test and bundle config set --local path vendor/bundle, or BUNDLE_WITHOUT and BUNDLE_PATH.

solid answer

~40 s

In Bundler 2, `bundle install --without test --path vendor/bundle` also wrote those values into `.bundle/config`, so every later command silently kept them. Bundler 4 dropped that behaviour, and `--without`, `--with`, `--path`, `--deployment`, `--frozen`, `--system`, `--clean`, `--shebang` and `--no-prune` now raise `InvalidOption`, naming the `bundle config set` command to use instead. The replacements are `bundle config set --local without test` and `bundle config set --local path vendor/bundle`. `--local` writes `<project>/.bundle/config` (or `$BUNDLE_APP_CONFIG/config`), `--global` writes `~/.bundle/config`, and without either flag `set` writes locally inside a project. Every key has an environment form, `BUNDLE_WITHOUT`, `BUNDLE_PATH`, and Bundler reads local config first, then the environment, then global config, then defaults. With `path` set, gems land under `vendor/bundle/ruby/4.0.0`; without it, Bundler 4 still installs into RubyGems' own directory.

code

bash · 7 lines
bash
# Bundler 4: settings instead of remembered flags
bundle config set --local without "development test"
bundle config set --local path vendor/bundle
bundle install                      # gems under vendor/bundle/ruby/4.0.0

bundle config get without           # value and where it was set
BUNDLE_WITHOUT=test bundle install  # env form, loses to .bundle/config

go deeper

for a junior

Recall that Bundler 4 sets groups to skip and the install path with bundle config set, not with bundle install flags.

for a middle

Explain local versus global config files, the BUNDLE_ environment forms, the lookup order, and what path and without change on disk.

for a senior

Migrate Bundler 2 CI and Dockerfiles off remembered flags, prefer environment settings in images, and debug precedence with bundle config get.

for a principal

Standardise where each environment's Bundler settings live, image, CI or repository, so builds are reproducible without hidden per-machine config.

## Why the old command fails Bundler 2 had a feature many people never noticed: some `bundle install` flags were **remembered**. Running `bundle install --without test --path vendor/bundle` once also wrote `without` and `path` into `.bundle/config`, so every later `bundle install`, `bundle exec` and `Bundler.setup` quietly kept skipping `test` and using `vendor/bundle`. That surprised people constantly: a flag typed once months ago still shaped every install. Bundler 4 removed remembered flags entirely. The affected `bundle install` options are marked "(removed)" and raise `InvalidOption`: > The `--without` flag has been removed because it relied on being remembered across bundler invocations, which bundler no longer does. Instead please use `bundle config set without 'test'`, and stop using this flag | Removed flag | Replacement | |---|---| | `--without test` | `bundle config set --local without test` | | `--with debug` | `bundle config set --local with debug` | | `--path vendor/bundle` | `bundle config set --local path vendor/bundle` | | `--system` | `bundle config set --local path.system true` | | `--deployment`, `--frozen` | `bundle config set --local deployment true` / `frozen true` | (`--binstubs` is gone too, replaced by the `bundle binstubs` command.) ## Where configuration lives `bundle config set` writes to one of two files: - **`--local`** - `<project_root>/.bundle/config`, or `$BUNDLE_APP_CONFIG/config` when `BUNDLE_APP_CONFIG` is set. Applies to this project. - **`--global`** - `~/.bundle/config`. Applies to every project for this user. - **Neither** - `set` writes locally when run inside a project, globally otherwise. `bundle config get NAME` shows the value and where it was set; `bundle config` alone lists everything; `bundle config unset NAME` removes it. Every key also has an **environment variable** form: upper-case it and prefix `BUNDLE_` (dots become `__`). So `without` is `BUNDLE_WITHOUT` and `path` is `BUNDLE_PATH`. Bundler resolves a key in this order: 1. Local config (`.bundle/config`) 2. Environment variables 3. Global config (`~/.bundle/config`) 4. Bundler's defaults A stale `.bundle/config` in a project therefore beats a `BUNDLE_WITHOUT` exported in CI - a classic source of "why is it still skipping that group?" Check it with `bundle config get without`. ## `without`, `with` and `path` in practice - **`without`** - a space- or colon-separated list of groups not to install, such as `development test` for a production image. The skipped groups are still resolved and locked, so production runs the versions you tested. - **`with`** - groups to install that would otherwise be skipped, mainly groups declared `optional: true` in the Gemfile. - **`path`** - where gems are installed. Bundler appends the engine and ABI version, so `vendor/bundle` becomes `vendor/bundle/ruby/4.0.0/`. Without `path`, Bundler 4 installs into RubyGems' own directory (`Gem.dir`), shared with `gem install`; the bundle-config man page says Bundler 5 will default to a `.bundle` directory in the project instead. In a container build the environment form is usually cleanest, because nothing is written into the image's project directory: ```bash export BUNDLE_WITHOUT="development test" export BUNDLE_PATH=/usr/local/bundle bundle install ``` ## Neighbouring settings A few other keys are easy to confuse with these: - **`only`** (`BUNDLE_ONLY`) - install *only* the listed groups. Gems outside any group sit in `default`, so `only test:default` is usually what you mean. - **`path.system`** (`BUNDLE_PATH__SYSTEM`) - install into RubyGems' default system directory (`Gem.dir`); the replacement for `--system`. - **`bin`** (`BUNDLE_BIN`) - the directory `bundle binstubs` writes to. - **`BUNDLE_APP_CONFIG`** - moves the local config file out of `<project>/.bundle`, handy when the project directory is read-only in a container. - **`BUNDLE_IGNORE_CONFIG`** - makes `bundle` ignore all configuration files, a quick way to test whether a stray config is to blame. ## Traps - Copying Bundler 2 CI snippets that pass `--without` or `--path`; they now stop the build. - Committing `.bundle/config` with machine-specific values such as a local `path`. - Forgetting that local config outranks the environment.

  • CI exports `BUNDLE_WITHOUT=development`, yet `bundle install` also skips `test`. Where do you look?
    At local configuration: `bundle config get without` shows every place the key is set. A `.bundle/config` in the checkout, perhaps committed by mistake or restored from a cache, outranks environment variables, so its `without: development:test` wins. Delete or `bundle config unset --local without` it.
  • What changes when you set `path` to `vendor/bundle` instead of leaving it unset?
    Gems install under `vendor/bundle/ruby/<abi>/` inside the project rather than into RubyGems' shared directory, so `gem list` no longer shows them and each project gets an isolated copy. It is common in CI caches and deployments; `bundle exec` or binstubs are then required, since plain RubyGems does not look there.

saying these in an interview costs you the question

  • bundle install --without test still works in Bundler 4
  • Environment variables always override .bundle/config
  • bundle config set --global writes into the project's .bundle directory
  • Groups in without are left out of Gemfile.lock
  • Bundler 4 installs gems into a project .bundle directory by default