First, appologies in advance for a long and rambing post. If you are impatient, skip to the bottom as I have recommendations.
So far I’ve been contributing pull requests for which there is essentially no downsides. I have a few more but I’m rapidly approaching the point where there will be tradeoffs. And I’d like to get advice on how to proceed.
A concrete example: right now as it is set up if you add a gem to a Gemfile the bundle install
step in the dockerfile will be rerun. The whole step. It won’t just add the one gem, it will reinstall all gems.
This can be improved by the use of --mount=type=cache
. And this problem is not just locallized to gems, but also node modules and debian packages.
There is a clear benefit to using a cache. It also makes the Dockerfiles considerably less readable. I happen to like readable Dockerfiles. But I also like fast builds. But how often are you really changing your Gemfile, package.json, or needing to install a new Debian package.
One approach is to decide it is worth it. That’s what fly is currently doing (not yet for gems, but that’s merely an oversight/todo). Another is to provide a generator with options that allow the developer to identify their preference (with defaults that we decide). A third is to document how to make these changes.
There are more decisions like these. I’ve recently added multi-stage builds to the Dockerfile in order to keep the image small. They can also be used to do things like yarn install and bundle install in parallel. Similar tradeoff: small clean Dockerfiles that are fast most of the time vs larger Dockerfiles that are faster when dependencies change.
Dockerfile generation is only doing half of the job needed to deploy Rails API-only servers.
… I could go on, but I hope I’ve made my point.
There are other ramifications that aren’t quite so broad. As an example, per the docs a syntax directive is required in order to support --mount=type=cache
or here-docs. But is it required? Another page lists an advantage of syntax directives that it will “Make sure all users are using the same implementation to build your Dockerfile”. And continues to say “We recommend using docker/dockerfile:1”. So in other words, it may work or may not work without that directive. And finally, the best practices page includes the directive. But doesn’t
explicitly reiterate the recommendation.
And all for a feature that we have yet to determine how to handle in Rails.
I mentioned that am closing this with a recommendation. First the easy one. I’d like to see the syntax directive included to make editing Dockerfiles by developers more predicable.
For the harder one, I would like to see the building of a dockerfile split out as a generator. You see it today with new projects: bin/rails importmap:install
, bin/rails turbo:install stimulus:install
and the like.
I have multiple reasons for wanting this. Mostly because I want to make this available retroactively to Rails versions prior to 7.1, as well as to make it available to projects that upgrade to 7.1. In essence, I want to retire some code that I have that produces Dockerfiles.
I’d also like to see the generator able to be rerun on projects with existing Dockerfiles. One use would be to change an option like one of the ones mentioned above. Another is to have it scan your Gemfile and package.json to see if there are other debian packages needed to be installed. We get support requests all the time with “it works on my machine but doesn’t work with the Dockerfile you provided” when this is the root cause. The way I’d like to handle this going forward is to investigate once what is needed to install, update the generator, and tell future customers to rerun the generator when this occurs.
And, yes, I’m volunteering. And, no, the above is not an exhaustive list of the types of problems I expect Rails to be facing with this addition. And, yes, I’m volunteering to address those too.