Multi-Stage Docker builds using Shippable

- By Ambarish Chitnis on October 10, 2017

Docker introduced a new feature called Multi-stage builds in Docker 17.05. This feature enables you to build an image in multiple stages, with each stage represented by a FROM statement. 

 A very common use-case that motivated the development of the feature is building a production image of an application that has a much smaller disk (storage) fooprint than the development image. In the first stage of the build, the application is compiled in an image that has the entire language toolchain. In the second stage of the build, the built application and its runtime dependencies ONLY are copied over to a different base image. The process of copying selective artifacts from one stage to another is thus greatly simplied in a single Multi-stage Dockerfile. To learn more about this feature, see Docker's documentation here.

Shippable supports Multi-stage Docker builds out of the box. In this blog, we will learn how to build a docker image using a Multi-stage Docker file for a Hello-World goLang application.  

 

Multi-Stage Dockerfile

Let's take a look at our Multi-Stage Dockerfile. 

FROM drydock/u16golall:v5.8.2 as builder
WORKDIR /hello-world
COPY ./hello-world.go .
ENV PATH="/root/.gvm/gos/go1.8.3/bin:${PATH}"
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o hello-world .
FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/
# This command copies the build artifact from Stage 1 to this stage COPY --from=builder /hello-world . CMD ["./hello-world"]

 

Lets look at the different stages -

  • STAGE 1: We use the goLang base image provided by Shippable that has the entire goLang toolchain preinstalled and builds our hello-world application. This stage is labelled as builder
  • STAGE 2: We use a slimmed down alpine image and copy the built hello-world application (with the statically linked goLang runtime).

Now we will proceed to building this slimmed down, production alpine based image of our hello-world go application in Shippable.

 

Sample Code

The source code for the Shippable sample can be found here. Feel free to clone the application and tweak it. 

 

1. Enable your repository for CI

To get started with Shippable CI, you need to do three things:

  • Sign in with your GitHub or Bitbucket credentials.
  • Create the CI config file shippable.yml and commit it to the root of your repository. The next section will go into the details of how to author shippable.yml file. If you're using a fork of our sample repository, you do not need to do this step since the repo already contains the config file. 
  • Enable your repository so that we can set up webhooks on your behalf. 
  • Set your subscription to use v5.8.2 machine image using instructions found here. This is important since this image comes preinstalled with Docker 17.06.0-ce, which supports Multi-stage docker builds.

 

2. Author shippable.yml

language: none

build:
  ci:
    # Dockerfile is present in the root of the repository.
    # We use Shippable platform environment variables $BRANCH and $BUILD_NUMBER
    # for tagging the docker image.
    - docker build -t devopsrecipes/multi-stage-docker:$BRANCH.$BUILD_NUMBER .
    # After the image is built, we run the image to test if it prints Hello World.
    - docker run devopsrecipes/multi-stage-docker:$BRANCH.$BUILD_NUMBER
    # The image can now be pushed to any supported Docker registry.
# For more information, please see this document - http://docs.shippable.com/ci/push-artifacts/.

 

3. Trigger a new build manually

After authoring your shippable.yml file and enabling your project, the next step is to trigger a manual build of your project to build and test your go image . 

To learn about how to manually trigger a build, look at this doc.

Here is how your build output might look.

Screen Shot 2017-10-09 at 11.13.20 PM.png

 

You can see the hello-world output of our goLang application running on an Alpine Linux container.