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 <joseph@marhee.me>
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.
Top comments (0)