How to build spring boot service to docker image with jib

Spring Boot is a very popular framework in java-world now. If you package it to docker-images in common style, it will be so heavy. And it can solved!

In this post I want to discuss image size. What size affects:

  1. Occupied space in storage. Now, hard disks are cheap, but if each image has size 2GB, it will occupy a big volume in the future.
  2. Downloading speed in the update process.
  3. Traffic volume in the update process.
  4. Data duplication in an image. E.g. spring boot fat jar. It consists of all the dependencies. And each of image consists its. But we can store it to separate layers.

What can we do?

  1. Push each dependency as layer to docker registry.
  2. Use distro less base images.
  3. Use cropped java with only needed dependency.

How can we do?

Use Jib Jib.

OK, do that!

Initial state

I have a backend for my aviation application. Docker image has a size 354.12 MiB.

Docker image size

Its Dockerfile:

FROM openjdk:11.0.3-jdk-stretch

MAINTAINER Alexey Nevinsky <alexey@nevinsky.net>

COPY devops/docker/backend/runApp.sh /opt/backend/runApp.sh
ADD projects/app-backend/build/libs/app-backend.jar /opt/backend/app.jar

CMD opt/backend/run_app.sh

Its runApp.sh file:

#!/bin/bash

echo "Application Name: backfire backend"
java ${JVM_OPTS} \
  -Djava.security.egd=file:/dev/./urandom \
  -jar ./opt/backend/app.jar \

File .gitlab-ci.yml for build:

variables:
  GRADLE_OPTS: "-Dorg.gradle.daemon=false"

stages:
  - build
  - deploy

before_script:
  - export GRADLE_USER_HOME=`pwd`/.gradle

cache:
  paths:
    - .gradle/wrapper
    - .gradle/caches
    - build/libs

build:
  image: openjdk:11.0.3-jdk-slim-stretch
  stage: build
  script: ./gradlew build -xtest
  artifacts:
    paths:
      - projects/app-*/build/libs/*.jar
    expire_in: 1 week
  only:
    - develop

deploy:
  image: docker:stable
  services:
    - docker:dind
  stage: deploy
  script:
    - docker info
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

    - export DOCKER_IMAGE="backend"
    - export DOCKER_IMAGE_NAME=${CI_REGISTRY_IMAGE}/${DOCKER_IMAGE}
    - docker build -t ${DOCKER_IMAGE} -f ./devops/docker/${DOCKER_IMAGE}/Dockerfile .
    - docker tag ${DOCKER_IMAGE} ${DOCKER_IMAGE_NAME}:latest
    - docker push ${DOCKER_IMAGE_NAME}:latest

  only:
    - develop

Build time with compilation, build docker image, and push to the registry is 06:18.

Well, rewrite the project to build with jib and see results again.

One of the jib’s benefits is: we don’t need docker on build machine!

Rewrite Gradle build scripts

  1. Add a plugin to build.gradle in root:
plugins {
    id 'info.solidsoft.pitest' version '1.4.0'
    id "net.ltgt.apt" version "0.21"
    id 'com.palantir.git-version' version '0.11.0'
    id 'com.google.cloud.tools.jib' version '2.1.0' //<<<<<< here
}
  1. Add a plugin to the project’s build.gradle and jib config:
project.description = 'Aviation backend project'

apply plugin: "com.google.cloud.tools.jib" // <<<< here

dependencies {
    implementation libraries.spring_boot_starter_web
    implementation libraries.java_mail
}

/// And here
jib {
    container.mainClass = "net.nevinsky.BackendApp"
    to {
        image = "registry.gitlab.com/inver/backfire/backend"
    }
}

Rewrite .gitlab-ci.yml

image: openjdk:11-jdk-slim

stages:
  - build

before_script:
  - export GRADLE_USER_HOME=`pwd`/.gradle

cache:
  paths:
    - .gradle/wrapper
    - .gradle/caches

build:
  stage: build
  script:
    - ./gradlew build :backend:jib -Djib.to.auth.username="$CI_REGISTRY_USER" -Djib.to.auth.password="$CI_REGISTRY_PASSWORD" -Djib.httpTimeout=120000

after_script:
  - echo "End CI"

Please, do not forget double quotes, then you will specify username and password!

Commit this file to GitLab and wait for the success ci pipeline.

For my project, the time of compilation and push be 01:57, size of docker image: 85.50 MiB. PROFIT! =)

P.s. Also, you can use this tool for exploring docker images: Dive

Previous post » A simple seo checker