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"]
At a high level, here are the things we're optimizing in our Docker build for a Ruby application with Bundler:
FROM ruby:3.4 AS build
FROM ruby:3.4 AS build
We 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.
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 coresRUN --mount=type=cache,target=/usr/local/bundle/cache \
--mount=type=cache,target=/app/vendor/cache \
bundle cache && \
bundle install && \
bundle clean --force
We install gems with dual cache mounts for maximum build efficiency:
bundle cache
downloads and caches gems locally before installationbundle install
installs the cached gemsbundle clean --force
removes any gems not in the current Gemfile, keeping the installation cleanThe dual cache mounts optimize both Bundler's internal cache and the vendor cache directory.
COPY . .
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 appuser
The runtime stage uses Ruby 3.4 slim image for a smaller footprint and creates a non-root user for security:
groupadd
creates a group with GID 1001useradd
creates a user with UID 1001, home directory, and bash shellWORKDIR /app
COPY --from=build --chown=appuser:appgroup /app .
COPY --from=build --chown=appuser:appgroup /usr/local/bundle /usr/local/bundle
We copy the application and installed gems from the build stage with proper ownership.
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=production
sets the Rails environmentRUBY_YJIT_ENABLE=1
enables YJIT for improved Ruby performance (Ruby 3.1+)BUNDLE_WITHOUT=development:test
ensures development gems aren't loadedCache mounts are one of the most powerful features for optimizing Docker builds with Depot. 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 --force
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 cacheFor more information regarding Bundler cache mounts, please visit the official Bundler documentation.