UPDATE: We've come up with a potentially better way to achieve the same goal, by using a new
Gemfile.initialfile. See the new post here.
I wrote a small script that can sync
<GEM>_VERSION environment variables in a
Dockerfile with versions from
For example, if you have the following files:
ENV RAKE_VERSION="12.0.0" \ RAILS_VERSION="5.0.0" \ NOKOGIRI_VERSION="1.0.0"
rake (13.0.6) ... rails (6.0.5) ... nokogiri (1.13.6)
You can run this script to parse the versions from your
Gemfile.lock and update your
Dockerfile with the current versions:
#!/bin/bash set -euo pipefail ROOT_DIR="$(realpath $(dirname "$0")/..)" ( cd $ROOT_DIR # Set Dockerfile gem versions from Gemfile.lock for GEM_NAME in rake rails nokogiri; do GEM_VERSION="$(grep " $GEM_NAME (\d*\.\d*\.\d*)" Gemfile.lock | grep -o '\d*\.\d*\.\d*')" GEM_NAME_UPCASE=$(echo $GEM_NAME | tr '[:lower:]' '[:upper:]') sed 's/'$GEM_NAME_UPCASE'_VERSION="[^"]*"/'$GEM_NAME_UPCASE'_VERSION="'$GEM_VERSION'"/g' Dockerfile > Dockerfile.tmp mv Dockerfile.tmp Dockerfile done )
I like to use a
ROOT_DIRvariable in my scripts so that I can call them from any directory.
You can also run this script automatically whenever you call
bundle install. To do this, add a
Gem.post_install hook to your
Gem.post_install do next if @updated_dockerfile system('scripts/update_dockerfile_versions.sh') @updated_dockerfile = true end
Why would you need to do this?
It can take a long time to install gem dependencies when you're building a Docker image. To speed this up, you can put these lines near the top of your
COPY Gemfile Gemfile.lock ./ RUN bundle install
Docker will cache the
bundle install step, and it will only re-run this step if there are any changes in
You can speed this up even further if you install a couple of specific gems even earlier in your
Dockerfile. This can be especially helpful if there are some gems that take a long time to compile native extensions.
Dockerfile will always cache the current versions of
nokogiri, even if you've added or updated some other gems.
Can you parse the gem versions while building the Dockerfile?
You would need to copy the
Gemfile.lock into your Docker image in order to parse the versions. This would break the extra layer of caching we're trying to achieve, because
Gemfile.lock is updated whenever any gem is changed.
What about using an .env file or -e flags?
You could also pass these environment variables as flags. You could write a script that wraps the
docker commands and sets these environment variables. I think it might just be easier to update the versions directly in your
Dockerfile, especially if you can automate it with a
Let us know if you have any suggestions or a better way to do this!
We don't have a commenting feature on our blog, but please feel free to send us an email: [email protected]