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
toproduction
, and setBUNDLE_ONLY
based onRAILS_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 aslibsqlite3-dev
if using thesqlite3
gem orlibpq-dev
if using thepg
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 apackage.json
file with anengines.node
value. -
Set
NODE_ENV
toproduction
. -
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 actualSECRET_KEY_BASE
from your credentials file, set theRAILS_MASTER_KEY
orconfig/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 aslibsqlite3-0
if using thesqlite3
gem orpostgresql-client
if using thepg
gem. -
Set
RAILS_LOG_TO_STDOUT
andRAILS_SERVE_STATIC_FILES
for Rails applications generated prior to Rails 7.1. (Seerails/rails@2b1fa89
andrails/rails@e8f481b
.) -
Add an unprivileged user named
rails
, and setUSER
asrails
. -
Set
WORKDIR
for the final application stage as/rails
. -
Set
ENTRYPOINT
as your application'sbin/docker-entrypoint
andCMD
asbin/rails server
. If your application doesn't have abin/docker-entrypoint
file, a fallback will be used that injects a call tobin/rails db:prepare
whenever the given command isbin/rails server
(or an alias thereof). -
Expose port 3000.
-
Lastly, the
Dockerfile
copies the artifacts from the builder stage to the final application stage withCOPY --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
orRAILS_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
orCMD
, such as to startforeman
: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 /