Turn-key Docker base images for Rails apps
MIT License
railbarge
is a set of Docker images that uses ONBUILD
instructions plus
dockhand
to provide a turn-key Dockerfile
UX for Rails applications.
For example, the following Dockerfile
will work for many Rails applications:
ARG RUBY_VERSION=3.2.0
FROM railbarge/builder:ruby-$RUBY_VERSION as builder
FROM railbarge/app:ruby-$RUBY_VERSION
COPY --from=builder /artifacts /
It will:
Set RAILS_ENV
to production
, and set BUNDLE_ONLY
based on RAILS_ENV
in
order to limit which packages and gems are installed (in both the builder and
final application stages).
Install buildtime-only apt packages in the builder stage based on common
gems in your Gemfile
, such as libsqlite3-dev
if using
the sqlite3
gem or libpq-dev
if using the pg
gem.
Install gems as artifacts in the builder stage.
Install Node.js in the builder stage if your application has a
.node-version
file or a package.json
file with an engines.node
value.
Set NODE_ENV
to production
.
Install Node.js modules as artifacts in the builder stage if your application has a Yarn, NPM, or PNPM lock file.
Copy your application code as an artifact in the builder stage.
Precompile gem and application code with bootsnap
if present.
Precompile assets with a dummy SECRET_KEY_BASE
if your application uses the
asset pipeline. To use the actual SECRET_KEY_BASE
from your credentials
file, set the RAILS_MASTER_KEY
or config/master.key
build secret:
$ RAILS_MASTER_KEY="..." docker build --secret id=RAILS_MASTER_KEY -t my_cool_app .
$ docker build --secret id=config/master.key -t my_cool_app .
$ docker build --secret id=config/master.key,src=config/credentials/production.key -t my_cool_app .
Fix binstubs in your application's bin/
directory if they were generated on
Windows.
Install runtime apt packages in the final application stage based on common
gems in your Gemfile
, such as libsqlite3-0
if using the
sqlite3
gem or postgresql-client
if using the pg
gem.
Set RAILS_LOG_TO_STDOUT
and RAILS_SERVE_STATIC_FILES
for Rails
applications generated prior to Rails 7.1. (See rails/rails@2b1fa89
and rails/rails@e8f481b
.)
Add an unprivileged user named rails
, and set USER
as rails
.
Set WORKDIR
for the final application stage as /rails
.
Set ENTRYPOINT
as your application's bin/docker-entrypoint
and CMD
as
bin/rails server
. If your application doesn't have a bin/docker-entrypoint
file, a fallback will be used that injects a call to bin/rails db:prepare
whenever the given command is bin/rails server
(or an alias thereof).
Expose port 3000.
Lastly, the Dockerfile
copies the artifacts from the builder stage to the
final application stage with COPY --from=builder /artifacts /
.
Behind the scenes, the above steps use --mount=type=cache
where
appropriate to reduce build times and to prevent unnecessary files from being
included in the final image.
Many of the above steps can be configured via build args. For example:
# Override the Rails environment and the gem group to install (default: "production")
ARG RAILS_ENV="staging"
# Specify multiple Rails environments to install gems for (default: RAILS_ENV)
ARG RAILS_ENVIRONMENTS="staging:test"
# Specify additional apt packages for the build stage
ARG BUILDTIME_PACKAGES="libmagickwand-dev"
# Specify additional apt packages for the final application stage
ARG RUNTIME_PACKAGES="imagemagick sudo"
# Override the Node.js environment (default: "production")
ARG NODE_ENV="development"
# Specify the subdirectory of your application that contains the package.json
# file, such as for a dedicated front-end client (default: ".")
ARG PACKAGE_JSON_DIR="frontend"
# Override the user name (default: "rails")
ARG USER="dude"
# Override the application directory name and WORKDIR (default: "/rails")
ARG APP_DIR="/my_cool_app"
ARG RUBY_VERSION=3.2.0
FROM railbarge/builder:ruby-$RUBY_VERSION as builder
FROM railbarge/app:ruby-$RUBY_VERSION
COPY --from=builder /artifacts /
(Note that the ARG
statements must come before the first FROM
statement;
otherwise, the railbarge
images will not see them.)
And, of course, you can append instructions to the stages themselves. For example, you can...
Disable RAILS_LOG_TO_STDOUT
or RAILS_SERVE_STATIC_FILES
for Rails
applications generated prior to Rails 7.1:
FROM railbarge/builder:ruby-2.7.7 as builder
FROM railbarge/app:ruby-2.7.7
ENV RAILS_LOG_TO_STDOUT=""
ENV RAILS_SERVE_STATIC_FILES=""
COPY --from=builder /artifacts /
Enable YJIT:
FROM railbarge/builder:ruby-3.2.0 as builder
FROM railbarge/app:ruby-3.2.0
ENV RUBY_YJIT_ENABLE="1"
COPY --from=builder /artifacts /
Switch to jemalloc:
ARG RUNTIME_PACKAGES="libjemalloc2"
FROM railbarge/builder:ruby-3.2.0 as builder
FROM railbarge/app:ruby-3.2.0
ENV LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
ENV MALLOC_CONF="dirty_decay_ms:1000,narenas:2,background_thread:true"
COPY --from=builder /artifacts /
Override ENTRYPOINT
or CMD
, such as to start foreman
:
FROM railbarge/builder:ruby-3.2.0 as builder
FROM railbarge/app:ruby-3.2.0
COPY --from=builder /artifacts /
ENTRYPOINT ["bin/my-entrypoint"]
CMD ["foreman", "start"]
Run an extra NPM build step, such as for a dedicated front-end client:
ARG PACKAGE_JSON_DIR="client"
FROM railbarge/builder:ruby-3.2.0 as builder
RUN cd client \
&& npm run build \
&& mv build/* ../public
FROM railbarge/app:ruby-3.2.0
COPY --from=builder /artifacts /
Install Node.js as a build artifact so that it will be available at runtime, such as to use Puppeteer:
# Install Chromium for Puppeteer:
ARG RUNTIME_PACKAGES="chromium"
FROM railbarge/builder:ruby-3.2.0 as builder
# Install Node.js as a build artifact:
RUN dockhand install-node --prefix=/artifacts/usr/local
FROM railbarge/app:ruby-3.2.0
# Point Puppeteer to Chromium:
ENV PUPPETEER_EXECUTABLE_PATH="/usr/bin/chromium"
# Copy installed Node.js along with other artifacts:
COPY --from=builder /artifacts /