Docker supports the capability to use the Dockerfile declaration to perform an image build in multiple steps called a multi-stage build.
A while ago, I built a Ruby application that provisions and deploys a Docker-based VPN server on DigitalOcean that, itself, runs in a Docker container. Before it ran on Kubernetes, however, I ran it on pre-imaged hosts that had dependencies baked into it, which make spinning up new instances very quick.
I had the same capability in Docker, to build a base image in an earlier stage (incidentally, using the same CI/CD tooling I used for my base instance images), so when I went to deploy it, I would have those available and not have to perform the entire build at deploy-time. However, initially, I didn't know this, and had a Dockerfile that looked like this:
FROM ruby:2.2.4 ADD app.rb /app/app.rb ADD Gemfile /app/Gemfile ADD views/index.erb /app/views/index.erb ADD views/confirmation.erb /app/views/confirmation.erb ADD environment.rb /app/environment.rb WORKDIR /app RUN bundle install ENTRYPOINT ["ruby","app.rb","-o","0.0.0.0"]
It got the job done, but took a long time each time I deployed the rebuilt image because of the added steps further down my pipeline each time the image was touched.
So, I created a base image that only handled installing the Gem dependencies first, as its own step:
FROM ruby:2.2.4 as rb-base ADD Gemfile /root/Gemfile WORKDIR /root RUN bundle install
I will reference resources from this image through the alias I gave it above,
rb-base, and move on to the image I will end up pushing, so I'll append the following to the Dockerfile:
FROM ruby:2.2.4 as rb-app MAINTAINER Joseph D. Marhee <email@example.com> WORKDIR /app COPY --from=rb-base /usr/local/bundle/ /usr/local/bundle/ ...
to copy the gems I installed in the previous stage, and then proceed with the rest of the application handling:
... ADD app.rb /app/app.rb ADD Gemfile /app/Gemfile ADD views/index.erb /app/views/index.erb ADD views/confirmation.erb /app/views/confirmation.erb ADD environment.rb /app/environment.rb ENTRYPOINT ["ruby","app.rb","-o","0.0.0.0"]
to complete the image I'll ultimately run.
Going forward, unless you need to update your base image (which this allows you to handle as a separate task), you can target the new application only image stage
rb-app (i.e. in a CI system that builds and pushes to your registry):
docker build --target rb-app -t coolregistryusa.biz/jmarhee/app:latest .
and make the image available from the registry per usual from there.