Optimal Dockerfile for Ruby on Rails with Bundler
Below is an example Dockerfile that we recommend at Depot for building images for Ruby on Rails applications with Bundler.
# syntax=docker/dockerfile:1
FROM ruby:3.4 AS build
WORKDIR /app
ENV RAILS_ENV=production
COPY Gemfile ./
RUN bundle config set --local without 'development test' && \
bundle config set --local jobs $(nproc)
RUN --mount=type=cache,target=/usr/local/bundle/cache \
--mount=type=cache,target=/app/vendor/cache \
bundle cache && \
bundle install && \
bundle clean --force
COPY . .
FROM ruby:3.4-slim AS runtime
RUN groupadd -g 1001 appgroup && \
useradd -u 1001 -g appgroup -m -d /home/appuser -s /bin/bash appuser
WORKDIR /app
COPY --from=build --chown=appuser:appgroup /app .
COPY --from=build --chown=appuser:appgroup /usr/local/bundle /usr/local/bundle
RUN mkdir -p tmp/pids tmp/cache log storage && \
chown -R appuser:appgroup tmp log storage
ENV RAILS_ENV=production \
RUBY_YJIT_ENABLE=1 \
BUNDLE_WITHOUT=development:test
USER appuser
ENTRYPOINT ["bundle", "exec", "puma", "-C", "config/puma.rb"]Explanation of the Dockerfile
At a high level, here are the things we're optimizing in our Docker build for a Ruby application with Bundler:
- Multi-stage builds for cleaner separation
- Bundler cache mounts for faster dependency installation
- YJIT enabled for improved Ruby performance
- Security optimizations with non-root users
Stage 1: FROM ruby:3.4 AS build
FROM ruby:3.4 AS buildWe use the official Ruby 3.4 image as our build stage base. This provides a full Ruby environment with all necessary build tools for compiling native gems.
Environment and dependency configuration
WORKDIR /app
ENV RAILS_ENV=production
COPY Gemfile ./
RUN bundle config set --local without 'development test' && \
bundle config set --local jobs $(nproc)We set the production environment and configure Bundler:
without 'development test'excludes development and test gemsjobs $(nproc)enables parallel gem installation using all available CPU cores
Gem installation with caching
RUN --mount=type=cache,target=/usr/local/bundle/cache \
--mount=type=cache,target=/app/vendor/cache \
bundle cache && \
bundle install && \
bundle clean --forceWe install gems with dual cache mounts for maximum build efficiency:
bundle cachedownloads and caches gems locally before installationbundle installinstalls the cached gemsbundle clean --forceremoves any gems not in the current Gemfile, keeping the installation clean
The dual cache mounts optimize both Bundler's internal cache and the vendor cache directory.
COPY . .Stage 2: FROM ruby:3.4-slim AS runtime
FROM ruby:3.4-slim AS runtime
RUN groupadd -g 1001 appgroup && \
useradd -u 1001 -g appgroup -m -d /home/appuser -s /bin/bash appuserThe runtime stage uses Ruby 3.4 slim image for a smaller footprint and creates a non-root user for security:
groupaddcreates a group with GID 1001useraddcreates a user with UID 1001, home directory, and bash shell
WORKDIR /app
COPY --from=build --chown=appuser:appgroup /app .
COPY --from=build --chown=appuser:appgroup /usr/local/bundle /usr/local/bundleWe copy the application and installed gems from the build stage with proper ownership.
Application setup and permissions
RUN mkdir -p tmp/pids tmp/cache log storage && \
chown -R appuser:appgroup tmp log storage
ENV RAILS_ENV=production \
RUBY_YJIT_ENABLE=1 \
BUNDLE_WITHOUT=development:test
USER appuser
ENTRYPOINT ["bundle", "exec", "puma", "-C", "config/puma.rb"]We create necessary directories for Rails runtime files (PIDs, cache, logs, storage) with correct permissions and configure the runtime environment:
RAILS_ENV=productionsets the Rails environmentRUBY_YJIT_ENABLE=1enables YJIT for improved Ruby performance (Ruby 3.1+)BUNDLE_WITHOUT=development:testensures development gems aren't loaded- Puma web server with configuration file
Understanding BuildKit Cache Mounts
Cache mounts in this Dockerfile speed up builds by persisting the package manager cache. This means that even when a layer needs to be rebuilt, your package manager only fetches what's new or updated. This Dockerfile uses dual cache mounts:
RUN --mount=type=cache,target=/usr/local/bundle/cache \
--mount=type=cache,target=/app/vendor/cache \
bundle cache && \
bundle install && \
bundle clean --forceCache Mount Parameters Explained
-
type=cache: Specifies this is a cache mount that persists across builds. -
Bundler cache mounts:
target=/usr/local/bundle/cache: Mount point for Bundler's internal gem cachetarget=/app/vendor/cache: Mount point for vendored gem cache
For more information regarding Bundler cache mounts, please visit the official Bundler documentation.