I don’t know how to interpret the lack of response since I’ve posted the generator. I plan to put this in production next week. If you have any feedback, let me know. Meanwhile, I’ll continue to watch the Rails repository for changes.
Got dragged into some other tasks. Will review soon. But great to just continue rapid iteration on the repo now, then when I have another opening I can review in bulk
The generator now has a growing test suite. Perusing the expected results can quickly get you an overview of what the generator is capable of:
If you want to know what combination of options were used to produce any given results, look at the corresponding test case here:
The latest dockerfile-rails gem will write out options used to config/dockerfile.yml
, and will use those options as the default for the next run.
If Rails new uses this gem, it can pick default options.
I will be posting a webpage suggesting different sets of options that may work well with fly.io. Other cloud hosting services can do likewise.
And, of course, developers can ignore all of our advice(s) and select their own preferences.
For many that will mean that upgrading to a new version of Ruby or adapting after installing a new gem that requires a library to be installed will be a matter of:
bin/rails generate dockerfile
… accepting the changes (or specifying --force
) and committing the results.
Here’s the fly docs on options: Dockerfiles and fly.toml · Fly Docs
The plan is to go live on Sunday: Cut over to Rails Dockerfile Generator on Sunday 29 Jan 2023 - rails - Fly.io
What this means is that I believe the gem is feature complete at this point. It also means that it is about to get significant real world usage and testing.
Just checking in. What fly.io needs (and I strongly suspect mrsk would greatly benefit from) is a dockerfile generator that can be run on existing projects, and rerun as dependencies change, rather than a providing a starter Docker image after which you are on your own.
- I’ve fixed many bugs. I’ve stopped mentioning them here as I haven’t seen interest in making the fixes. An example:
Gem.ruby_version
can produce a four part version number, but ruby-slim docker images only have three part version numbers. - libvips is not the only debian package that Rails applications might need. There are many popular packages with similar dependencies. Packages like grover and execjs require even more.
- I’ve seen many “API-only” applications with a JS front end that, from a deploy, perspective are very much like jsbunding rails applications, but running npm run build rather than assets:precompile. Some seem to like structuring their repository as a mono-repository with subdirectories containing gems or engines. We should support these types of architectures too.
- being able to run the generator when the project is 'ready", and rerun it as needed, means that the Dockerfile can include only what is needed by the application and/or explicitly requested rather than including what might be needed down the road. This results in smaller images and faster load times.
- There are plenty of build and runtime optimizations that can be opted into. And I’ve got more planned. As an example, some believe that best practices is to not run your rails application as root. I’m fine leaving the default as root, but by specifying an option a “ruby” or “rails” user can be created and the ENTRYPOINT/CMD can be run under this user.
I truly believe that this belongs some place more permanent than my personal github; if you agree lets find a home for it. If not, that’s fine and I’ll move it to the superfly organization, but continue to make it available to all.
Will definitely review before we get on the release train with 7.1. I remain in favor of the broad concept, but I also want to be careful this doesn’t turn into a huge thing. If someone wants to get into running under a separate user, that to me is outside the scope of what Rails needs to provide as a starting point. Anyway, thanks for working on this!
On the topic of separate user, that seems to be a top recommendation of pretty much every page I can find via google searches of “Dockerfile security best practices”. Additionally for those users that wish to run docker containers that mount volumes from their host drive, matching their host UID/GID will often make things easier. For those reasons, it seems reasonable to me to provide an option to allow running rails under a user other than root. It only ends up being a few extra lines in the Dockerfile if you select this option, and no extra lines at all if you don’t.
In any case, you continue to be welcome to make use of the gem to provide a Dockerfile (which I recommend), harvest just the pieces that you find relevant, or ignore it completely.
On a personal note, decoupling my ability to make forward progress from your availability has done wonders for my sanity.
A few issues
- Check for db:prepare success before starting rails server via Dockerfile · Issue #47414 · rails/rails · GitHub
- Fix ruby version check (needs a string, not a Gem::Version instance) · Issue #47415 · rails/rails · GitHub
- Track yarn version · Issue #47416 · rails/rails · GitHub
- Change from Gem.ruby_version to RUBY_VERSION · Issue #47417 · rails/rails · GitHub
Glad to see that the Rails template now includes a user.
Also, I fully agree with Inspiration is perishable
It looks like @DHH would like to see us stick to a single Dockerfile generated at “rails new” time.. I’ve written down why I don’t think that will work for most Rails developers in motivation.
Meanwhile, some additional thoughts:
- volta doesn’t work on ARM machines, so the current Rails dockerfile can’t be run on a Mac M1/M2 machine if you select a
--javascript
option onrails new
. - setting
BUNDLE_PATH="vendor/bundle"
enables you to eliminate the copy of/usr/local/bundle
resulting in a significantly smaller image size without making the dockerfile any more complicated - running out of memory is a real problem, particularly for those trying out a free tier of a cloud offering. It doesn’t make for a very good first impression.
- while build cache and other options produce less readable Dockerfiles they solve real problems. I’m fine with them not being the default, but they should be easy to opt in to.
In any case, I am working on dockerfile-rails because I need it. Everybody is welcome to either make use of it directly or steal the pieces they need.
@Sam_Ruby If you’re looking for an end to end solution that handles running things as a non-root user with an ability to customize the UID / GID at build time (defaults to 1000:1000
) there’s docker-rails-example/Dockerfile at main · nickjj/docker-rails-example · GitHub.
It works out of the box for most set ups where it’s expected you may have volumes, including:
- Docker Desktop
- Native Linux where the first user on your system is the user running Docker commands
- Any deployment environment where you control the machine (such as a single server VPS deploy)
If needed you can set UID=
and GID=
to be whatever custom value you need and rebuild.
I wrote a guide / video about it here: Running Docker Containers as a Non-root User with a Custom UID / GID — Nick Janetakis
I’ve been running this set up for a while now, it works. Lots of folks who have gone through my Docker courses have used this on a range of different systems without issues.
The approach Rails is currently taking can be found here:
dockerfile-rails matches this approach unless generation of a docker-compose.yml is requested, then it produces results that I believe matches your guide. If you think either approach can be improved, I encourage you to create an issue or (better yet) a pull request in the relevant github repository.
I did create issues about this in rails/rails with an in depth break down with suggestions to improve the Dockerfile but it got closed pretty quickly. A few Rails core members didn’t seem enthusiastic with wanting to use -slim
, multi-stage builds and setting a non-root user – they said all of this can be solved with documentation.
By the way you can reduce RUN
layer steps in your set up by adding that RUN useradd rails
to the previous RUN
layer. There’s no negative to that since creating a user takes 1 second. The upside is by reducing the layer count your image will build faster (although in this cause it’s minor, but it’s still an improvement).
I’ve ran something similar to your set up for years in the past but you’ll encounter a number of issues, not setting --create-home
may cause issues with packages that depend on a home dir existing, not setting USER
as an env var will be an issue as well. Not setting --no-log-init
may cause massively sized Docker images in some cases and folks who don’t have 1000:1000
as their UID/GID won’t be able to use the image without permission issues.
All of these are addressed in the link I supplied above btw.
I’d just like to say a big “thank you” for your work on dockerfile-rails! I will continue using it rather than the default in 7.1. As a busy solo developer I have been trying on and off over many years to get a working production-ready Docker configuration. I’ve read many books, articles and watched many videos. But your clear and concise project is what’s finally taken me over the top to where I have finally achieved the goal! This is a very worthwhile project whether it’s included with Rails or not. I came to your project after experimenting with the new Rails 7.1 Dockerfile and realizing that it was several steps behind what I already knew, then I found your project. I think this will be a natural progression for many users.
I’ve encountered the same, and it takes perseverance. I’m in this for the long haul. I’m pleased to say that all of the issues you mentioned are now part of the Rails 7.1 template.
I will say that you won’t find this attitude in dockerfile-rails. I encourage pull requests and issues, particularly ones that check the actual state of the project or with flags to opt in when the recommendations don’t universally apply. Not everybody will need every possible recommendation, but making it easy to generate Dockerfiles to match different sets of configurations enables rapid experimentation and is a substantially better developer experience than making every change a research project which is what sending people to documentation ends up being.
Thanks for the kind words! I will say that I’m still learning, and the outcome of this discussion in particular will result in a number of improvements. I suggest that you periodically upgrade and regenerate your Dockerfile to take advantage of improvements as they make their way into dockerfile-rails.
Yes I’ve been doing this regularly as new versions are released!
I wanted to share a project that I’ve been working on to provide a turn-key Dockerfile
UX for Rails apps: railbarge
.
railbarge
is a set of Docker base images that use ONBUILD
instructions to perform build steps which are tailored to the app. So the following Dockerfile
will work as is for many Rails apps:
ARG RUBY_VERSION=3.2.1
FROM railbarge/builder:ruby-$RUBY_VERSION as builder
FROM railbarge/app:ruby-$RUBY_VERSION
COPY --from=builder /artifacts /
I also wanted to say thank you, @Sam_Ruby, for all of your work. I have learned a ton from your pioneering, and I deeply appreciate it. I especially appreciate the demos for dockerfile-rails
. They have been an extremely valuable resource.
Regarding railbarge
, the minimal demo, the Action Cable demo, and the esbuild demo all run with the above Dockerfile
as is.
The Neofetch demo requires the neofetch
apt package, so that needs to be added to the Dockerfile
:
ARG RUBY_VERSION=3.2.1
ARG RUNTIME_PACKAGES="neofetch colorized-logs"
FROM railbarge/builder:ruby-$RUBY_VERSION as builder
FROM railbarge/app:ruby-$RUBY_VERSION
COPY --from=builder /artifacts /
The Create React App demo requires the React app to be built with npm run build
, so that needs to be added to the Dockerfile
:
ARG RUBY_VERSION=3.2.1
ARG PACKAGE_JSON_DIR=client
FROM railbarge/builder:ruby-$RUBY_VERSION as builder
RUN cd client && npm run build && mv build/* ../public
FROM railbarge/app:ruby-$RUBY_VERSION
COPY --from=builder /artifacts /
And the Grover demo uses Puppeteer, which requires Chromium and Node.js at runtime, so those need to be added to the Dockerfile
:
ARG RUBY_VERSION=3.2.1
ARG RUNTIME_PACKAGES="chromium"
FROM railbarge/builder:ruby-$RUBY_VERSION as builder
RUN dockhand install-node --prefix=/artifacts/usr/local
FROM railbarge/app:ruby-$RUBY_VERSION
ENV PUPPETEER_EXECUTABLE_PATH="/usr/bin/chromium" \
GROVER_NO_SANDBOX="true"
COPY --from=builder /artifacts /
Feedback is welcome! If anyone has a use case that isn’t covered by the dockerfile-rails
demos, please do share! It may benefit dockerfile-rails
too!
Seriously impressive. We should try to find a way to collaborate. Things are still moving fast - just today there was a major change to the Rails Dockerfile concerning security.