Using Multi-stage Dockerfiles
Joseph D. Marhee
Posted on December 31, 2018
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.
Posted on December 31, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.