Final image installs a jemalloc package but does not preload it viaDocumentation Index
Fetch the complete documentation index at: https://tally.wharflab.com/llms.txt
Use this file to discover all available pages before exploring further.
LD_PRELOAD or MALLOC_CONF.
| Property | Value |
|---|---|
| Severity | Warning |
| Category | Performance |
| Default | Enabled |
| Auto-fix | Yes (apt/apt-get only, suggestion) |
Description
Long-running Rails worker processes fragment glibc malloc heaps over time, which manifests as steadily growing RSS — the canonical “Rails memory leak” that often turns out to be allocator behavior. The Ruby community has converged on jemalloc as the workaround: the Rails 7.1+ generator, Mastodon, GitLab, Discourse, and most production Rails Dockerfiles either linklibjemalloc.so and set LD_PRELOAD, or set jemalloc-specific
MALLOC_CONF knobs.
Installing a jemalloc package without doing one of those means glibc malloc is still in use — the package
takes ~350 KiB of disk and never loads. This rule fires when a final stage installs libjemalloc1,
libjemalloc2, libjemalloc-dev (Debian/Ubuntu), jemalloc, jemalloc-dev (Alpine), or
jemalloc-devel (RHEL/Fedora/CentOS) without a matching ENV LD_PRELOAD=…jemalloc… or
ENV MALLOC_CONF=… carrying a jemalloc-specific knob.
The rule recognizes any of the following as evidence jemalloc is loaded:
LD_PRELOADwhose value containsjemalloc(case-insensitive).MALLOC_CONFwhose value contains any of these jemalloc-only options:narenas:,background_thread:,dirty_decay_ms:,muzzy_decay_ms:,thp:.
dev, development, test, testing, ci, or debug are skipped, as are
non-final stages and Windows-based stages.
Because the rule lives in the Ruby namespace and its remedy is Rails-flavored, it only fires on stages
that look like a Ruby runtime: an official ruby:* base, a familiar name containing ruby or rails,
a Ruby-runtime derivative (e.g. phusion/passenger-ruby*, jruby, truffleruby), a stage env carrying
Ruby/Rails/Bundler signals (RUBY_VERSION, RAILS_ENV, BUNDLE_*, GEM_HOME, …), or an
ENTRYPOINT/CMD that invokes ruby, rails, bundle, puma, unicorn, passenger, sidekiq, etc.
A non-Ruby image installing jemalloc for unrelated reasons does not trip this rule.
Examples
Before
After
The Rails-generator-style fix links the architecture-correctlibjemalloc.so.2 into a stable path and
preloads it:
MALLOC_CONF to a tuning string — also satisfies the rule, because those knobs
are only honored by jemalloc:
Auto-fix
When the violating install command usesapt-get or apt, the rule offers a FixSuggestion that inserts
the missing pieces on the line immediately following the install RUN:
$(uname -m) form works on amd64 and arm64 Debian/Ubuntu images. The fix uses ln -sf (force) so
re-running on a layout that already has the symlink replaces it instead of failing the build with
File exists. Because the symlink target depends on the base image’s multiarch path layout, the fix is
FixSuggestion rather than FixSafe and requires --fix-unsafe to apply in batch mode.
If the stage already contains a ln -s … libjemalloc.so step and only forgot the ENV LD_PRELOAD, the fix
emits only the missing ENV line — no redundant symlink.
When the stage already sets a non-jemalloc ENV LD_PRELOAD=… (for example, an instrumentation/sanitizer
preload), the fix preserves that value by prepending the jemalloc path instead of overwriting it. The
dynamic linker honors space-separated LD_PRELOAD entries left-to-right, so jemalloc loads first while your
existing preloads remain in effect:
libjemalloc2 or libjemalloc-dev, which both ship
libjemalloc.so.2 at /usr/lib/<arch>-linux-gnu/. The legacy libjemalloc1 package ships libjemalloc.so.1
instead, so the canonical .so.2 target does not exist there — the violation still fires but no auto-fix is
emitted. Migrate to libjemalloc2 for production images.
For Alpine, RHEL/Fedora, openSUSE, and other distros the canonical path differs, so no auto-fix is offered;
the violation still fires. Add the equivalent symlink + ENV LD_PRELOAD=… (or MALLOC_CONF) for your
distro.