Automatically update Docker gem versions from Gemfile.lock
UPDATE: We've come up with a potentially better way to achieve the same goal, by using a new Gemfile.initial
file. See the new post here.
I wrote a small script that can sync <GEM>_VERSION
environment variables in a Dockerfile
with versions from Gemfile.lock
.
For example, if you have the following files:
Dockerfile
ENV RAKE_VERSION="12.0.0" \
RAILS_VERSION="5.0.0" \
NOKOGIRI_VERSION="1.0.0"
Gemfile.lock
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_DIR
variable 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 Gemfile
:
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 Dockerfile
:
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 Gemfile
or Gemfile.lock
.
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.
The following Dockerfile
will always cache the current versions of rails
, rake
, and 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-compose
and 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 Gem.post_install
hook.
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: engineering@docspring.com