Docker构建缓存bundle install,解决docker-compose下gems从头安装问题
Great question! Your current Dockerfile is absolutely heading in the right direction for caching bundle install—but let’s break down how well it works, and share some tweaks to make it even more reliable and efficient.
Is Your Current Setup Effective?
Yes, it will work for caching, and here’s why:
- Copying Gemfile/Gemfile.lock first: Docker’s layer caching relies on unchanged files to skip re-running steps. By copying these two files before your full application code, you ensure that
bundle installonly re-runs if your gem dependencies change (not every time you edit app code). - Setting
BUNDLE_PATH/GEM_*variables: Redirecting gem installation to/gems(outside your app’s$APP_HOME) prevents the subsequentCOPY . $APP_HOMEstep from overwriting or invalidating the gem cache. bundle check || bundle install: This command smartly skips reinstalling gems if all required versions are already present in/gems, which adds another layer of efficiency.
Optimizations for Better Caching & Production Readiness
Here are some adjustments to tighten up your setup:
1. Slim Down the Alpine Image
Replace apk update && apk add with apk add --no-cache—this eliminates the need for a separate apk update and avoids leaving package manager cache in your final image, reducing its size significantly.
2. Add Production-Focused Bundle Flags
For production builds, you should enforce strict gem versioning and skip unnecessary groups (like development/test):
ENV BUNDLE_WITHOUT="development test" \ BUNDLE_FROZEN="true" \ BUNDLE_DEPLOYMENT="true"
BUNDLE_FROZENensures you can’t install gems without updatingGemfile.lock, keeping dependencies consistent.BUNDLE_DEPLOYMENTinstalls gems in a way optimized for production (no documentation, strict version locks).
3. Ensure Proper Directory Permissions (Optional)
If you plan to run the container as a non-root user (a security best practice), pre-create the /gems directory with correct permissions:
RUN mkdir -p /gems && chown -R ruby:ruby /gems
(The official Ruby Alpine image uses the ruby user by default.)
4. Final Optimized Dockerfile
Here’s how your updated file might look:
FROM ruby:2.5.0-alpine # Install dependencies without leftover cache RUN apk add --no-cache build-base postgresql-dev nodejs git tzdata ENV APP_HOME /panabus-api RUN mkdir -p $APP_HOME WORKDIR $APP_HOME # Copy gem files first to leverage cache COPY Gemfile Gemfile.lock ./ # Configure bundle for production and cache ENV BUNDLE_PATH=/gems \ GEM_PATH=/gems \ GEM_HOME=/gems \ BUNDLE_WITHOUT="development test" \ BUNDLE_FROZEN="true" \ BUNDLE_DEPLOYMENT="true" # Install gems only if dependencies changed RUN bundle check || bundle install # Copy remaining app code COPY . $APP_HOME ENV RAILS_ENV=production # Add production tasks (e.g., asset precompilation) RUN bundle exec rails assets:precompile
Key Takeaway
Your core approach is solid—prioritizing gem file copying to leverage Docker’s layer caching is the standard way to avoid re-installing gems on every build. The optimizations above just make the setup more production-ready, efficient, and reliable.
内容的提问来源于stack exchange,提问作者hcarreras

