My team hosts our Rails application out of a Dockerfile. We have a few slow gems that are really slowing down our builds. (I'm looking at yougrpc
. 😠)
Is it possible to install a few gems before copying the Gemfile into our Dockerfile? This would allow Docker to cache those build steps, so we don't have to reinstall the slow gems every time the Gemfile changes.
I tried this, but bundle install
is still installing grpc
, sassc
and nokogiri
.
RUN gem install grpc --version 1.28.0
RUN gem install sassc --version 2.2.1
RUN gem install nokogiri --version 1.10.9
WORKDIR /app
ADD Gemfile Gemfile.lock .ruby-version /app/
RUN bundle install
You can think about splitting your gemfile, think about the following files.
slow-gems
ruby File.read('.ruby-version').strip
gem 'rubocop'
gemfile
ruby File.read('.ruby-version').strip
# add the "slow" gems to the gem-bundle so we do not have to redefine them.
instance_eval File.read('slow-gems')
gem 'flay'
dockerfile
WORKDIR /app
ADD slow-gems slow-gems.lock .ruby-version /app/
RUN bundle install --gemfile=slow-gems
ADD Gemfile Gemfile.lock /app/
RUN bundle install
This also prevents you from redefining all the gems in the docker image and the gemfile. The only problem you might encounter that the version in both lock-files will drift apart. For now, I don't have a solution for that, but this can also happen while using your current method of adding them to the dockerfile.
The second bundle install
will re-use the already installed gem from the slow-gems
-file, this takes less than a second.
Adition: Don't forget to use docker's built-in caching, else this will not be faster and does not help you.
Problem:
So let's understand the problem first. Whenever you try to spin up your application it takes so much time. I think I understand your problem very well.
Possible solutions:
This is a performance of docker files. we have 2 areas which are creating the problems, and 2 different solutions
Solutions:
Download gem files locally put in a folder and in dockerfile copy those gem files inside the container and then install.
Create a base image for your application. Create a base image from Ruby
/any other you like and install your gems
either using the above method:
gems
from the local folder and then paste inside the container
orRUN
command to install the gems. Whatever you like. This will be a one time process. With this, you can have your base image which already contains your time-consuming gems installed. Now inside your application dockerfile
(which is a responsible for the startup the application), you just need to use your own created base image instead of Ruby
or Linux
from Docker Hub
We will see how to build your own base pre-configured image.
Let us see step by step.
Let's follow this GitHub repo: https://github.com/dupinder/docker-ruby-gem-game
Folder Structure (This will help to understand this article)
dockerfile
FROM ruby:latest
RUN mkdir -p /gems
COPY /gems/grpc-1.28.0-universal-darwin.gem /gems/grpc-1.28.0-universal-darwin.gem
COPY /gems/sassc-2.2.1.gem /gems/sassc-2.2.1.gem
WORKDIR /gems
RUN gem install --force --local *.gem
Build an image from this dockerfile using follwing command
docker build --rm -f "dockerfile" -t ruby-gem-base-image:latest "."
Step 1:
I am using the base image as ruby, you can your whatever you want if you are using Linux then in next step you need to install Ruby
Step 2: Create a folder name gems in the container.
docker
container
.gem install --force --local *.gem
which help to install gems inside a local directory.With this, we solved 50% time-consumption. Now docker will never download gems from internet each time and install.
Now let us check is there any of our required gems installed our not. For that:
Run command docker images
we will have our newly build image ruby-gem-base-image
Run container in detached mode, so that we can exec
later on docker run -it -d ruby-gem-base-image
Run docker ps
to get container ID.
exec
as bash
inside container docker exec -it d28234630343 bash
.gem list
This will print a list of gems installed, and you will see your required gems there.See your gems are installed from local directory.
If you follow these steps your problem is resolved. But Now you need if before starting your app docker container already have installed gems.
For this problem, we can use ruby-gem-base-image
image as our ruby
application base image. If you remember GitHub repo we have an application directory which has one dockerfile
if we see that.
FROM ruby-gem-base-image:latest
CMD ["gem", "list"]
This is your dockerfile which can be used when you want to deploy an application, Use prebuilt docker image which has your gems.
I write the task gem list
to check is this container have gems from parent image or not.
I think this is a bit clear. Your problem will be resolved with this.
If you need any other help or need help to understand this process, please ask.
---------Dupinder.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With