Multi-stage Docker Build

How Multi-stage Build Works

Demonstrating docker multi-stage builds which helps to build smaller docker images. This process also referred as Docker’s BUILDER PATTERN

— ThirupathiReddy Vajjala

One of the most challenging things about building images is keeping the image size down.

Multi-stage builds are a new feature requiring Docker 17.05 or higher on the daemon and client.

It was actually very common to have one Dockerfile to use for development (which contained everything needed to build your application), and a slimmed-down one to use for production, which only contained your application and exactly what was needed to run it.

How Multi-stage Build Works

With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image.

Create Docker Image with SpringBoot Gradle Project

Lets build Docker Image for SpringBoot application using Gradle

Source code available at Gradle SpringBoot MultiStageBuild

STAGE-1: Copy required files(src, gradle wrapper, gradlew,build.gradle, gradle.properties) into docker image and build a final SpringBoot FAT JAR.

We are compiling our source inside docker image with specified JDK version, JENKINS server need not required JDK & Gradle installed in it.

STAGE-2: Copy Final FAT JAR from STAGE-1 , Build Docker image which uses JRE (No JDK required for runtime)

Dockerfile
#Stage - #1
FROM openjdk:8 AS GENERATE_FAT_FILE

# create folder inside docker env
ENV APP_HOME=/root/dev/app/

# make src folder to put source code
RUN mkdir -p $APP_HOME/src

#change your working directory
WORKDIR $APP_HOME

#copy required files to build final executable Jar
COPY build.gradle settings.gradle gradle.properties gradlew gradlew.bat $APP_HOME

#copy gradle wrapper
COPY gradle $APP_HOME/gradle

#copy main source code folder ( test folder not required inside the docker)
COPY src/main $APP_HOME/src/main

#  skip test stage , bootJar will create executable Jar in gradle which is FAT Jar
# (-x for skip stage, -i for INFO mode to see all stages)
RUN ./gradlew build -x test -i


#Stage - #2
#We need JRE now to run the app , NO JDK not required
FROM openjdk:8-jre
LABEL maintainer="tvajjala.in"

# create folder inside docker env
ENV HOTBOX=/root

WORKDIR $HOTBOX

#Copy FAT JAR from previous stage to create final docker image
COPY --from=GENERATE_FAT_FILE /root/dev/app/build/libs/* .

#copy wiremock contracts (this is specific to my app)
COPY contracts $HOTBOX/contracts

EXPOSE 8080

#since root folder have one FAT JAR , make sure the final jar name
# java -jar *.jar working inside image but not outside need to debug
CMD ["java","-jar","spring-boot-wiremock-1.0.jar"]
CMD java -cp /root/ org.springframework.boot.loader.JarLauncher not working . need to check the root cause, which avoid jar file name hard-coding.
Label each stage (in my case GENERATE_FAT_FILE) with some string to refer in next stage, which also improves readability.

Use below command to check the content inside the docker image

interactive_mode.sh
$ docker run -it <image_name> sh

Check the image history which contains only stage-2 history

build_history.sh
docker image history --no-trunc <IMAGE_NAME> > image_history.txt

Inspect docker as JSON

inspect.sh
$ docker image inspect <IMAGE_ID>
This approach also helps your Jenkins server to handover dockerize stage to slave pods. That means your Jenkins server doesn’t required to install any kind of environment setups(JDK, Gradle, Maven etc) to compile code.

Finally, run the below command from root folder of this project

build_docker_image
docker build . -t tvajjala/wiremock

Once image is ready run below command to start

run_image_locally
docker run -p 80:8080 tvajjala/wiremock

Open your browser http://localhost , make sure your machines 80 port not used by other service or change to different port

Remove all stopped images

cleanup_container
docker rm $(docker ps -a -q)

Remove all Intermediate images

cleanup_images
docker rmi $(docker images | grep "^<none>" | awk "{print $3}")

View local docker images and container

docker system df

Remove all dangling images and stopped container

prune.sh
docker system prune

Comments

Popular posts from this blog

IBM Datapower GatewayScript

Spring boot Kafka Integration

Spring boot SOAP Web Service Performance