> ## Documentation Index
> Fetch the complete documentation index at: https://tally.wharflab.com/llms.txt
> Use this file to discover all available pages before exploring further.

# tally/ruby/prefer-bundler-cache-mount

> `bundle install` doesn't use a BuildKit cache mount; native-extension gems will recompile on every build.

`bundle install` doesn't use a BuildKit cache mount on `${BUNDLE_PATH}/cache`.

| Property | Value                                                        |
| -------- | ------------------------------------------------------------ |
| Severity | Info (default) / Warning (project has native-extension gems) |
| Category | Performance                                                  |
| Default  | Enabled                                                      |
| Auto-fix | Yes (`FixSuggestion`, no-edit)                               |

## Description

BuildKit's `RUN --mount=type=cache,target=${BUNDLE_PATH}/cache` keeps the gem-extraction cache across
builds. Without it, every cache-busted `bundle install` re-fetches and (for native-extension gems)
recompiles every gem from scratch — typically 30s+ on a non-trivial Rails project, and minutes for
nokogiri-heavy or grpc-heavy projects.

The corpus shows only **7 of 196** Dockerfiles use cache mounts at all.

This rule fires when:

* The Dockerfile carries the `# syntax=docker/dockerfile:1` (or higher) pragma — without it,
  `--mount=type=cache` errors at build time.
* A Ruby-shaped stage runs `bundle install`.
* That `RUN` doesn't carry a `--mount=type=cache,target=...` whose target matches the Bundler cache
  directory.

Recognized cache-target paths:

* `${BUNDLE_PATH}/cache` (variable form, what the Rails generator template uses).
* `/usr/local/bundle/cache` (Rails generator's default `BUNDLE_PATH`).
* `/bundle/cache`, `/bundle-cache`, `/cache` (other commonly-seen layouts).

### Context-aware refinement

When tally is invoked with `--context` and `Gemfile.lock` is observable, severity escalates from `info` to
`warning` for projects with at least one native-extension gem (`nokogiri`, `pg`, `mysql2`, `sqlite3`,
`grpc`, `ffi`, `oj`, `bcrypt`, `nio4r`, `puma`, `rugged`, `protobuf`, `sassc`, `eventmachine`, `unf_ext`,
`racc`, `bigdecimal`). The rebuild-from-scratch cost of these gems is exactly what the cache mount fixes.

## Examples

### Before

```dockerfile theme={null}
# syntax=docker/dockerfile:1
FROM ruby:3.3-slim
COPY Gemfile Gemfile.lock ./
RUN bundle install
```

### After

```dockerfile theme={null}
# syntax=docker/dockerfile:1
FROM ruby:3.3-slim
COPY Gemfile Gemfile.lock ./
RUN --mount=type=cache,id=bundler,target=${BUNDLE_PATH}/cache,sharing=locked \
    bundle install
```

The `id=bundler` makes the cache reusable across Dockerfiles. The `sharing=locked` prevents corruption
when concurrent builds touch the same cache.

## Auto-fix

`FixSuggestion`. The rule emits a non-edit suggestion: rewriting `--mount=type=cache` onto multi-line `RUN`
chains is too easy to get wrong (continuation backslashes, existing `--mount` flags, heredoc bodies). The
user applies the suggestion manually.

## References

* [Docker BuildKit `RUN --mount=type=cache` documentation](https://docs.docker.com/build/cache/optimize/#use-cache-mounts).
* [Bundler cache layout](https://bundler.io/v2.5/man/bundle-cache.1.html) — describes
  `${BUNDLE_PATH}/cache` semantics.
