Making Docker image with SpringBoot(2.4) application (official scheme of Gradle version)

Overview of this article

This paper demonstrates how to make a springboot application based on gradle into a docker image through actual combat. The relevant software version information is as follows:

  1. Operating system: macOS Big Sur 11.2.2
  2. JDK: 1.8.0_211
  3. gradle: 6.8.3
  4. docker: 20.10.5
  5. springboot: 2.4.4

New java project

In order to be closer to the actual project, the actual java project is a multi module parent-child structure:

  1. Create a new project named Java demo, and its build.gradle content is as follows:
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter

// Relevant settings used by gradle itself
buildscript {
    // Warehouse
    repositories {
        // local
        mavenLocal()
        // Central warehouse
        mavenCentral()
        // Grand plugin
        maven {
            url 'https://plugins.gradle.org/m2/'
        }
    }

    // Variables used by sub modules
    ext {
        springBootVersion = '2.4.4'
    }
}

// plug-in unit
plugins {
    id 'java'
    id 'java-library'
    // With this declaration, the sub module can use the org.springframework.boot plug-in without specifying the version, but apply=false means that the current module does not use this plug-in
    id 'org.springframework.boot' version "${springBootVersion}" apply false
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}

// gradle wrapper specifies the version
wrapper {
    gradleVersion = '6.8.3'
}

// Take the current time
def buildTimeAndDate = OffsetDateTime.now()

// Generate string variables based on time
ext {
    projectVersion = project.version
    buildDate = DateTimeFormatter.ISO_LOCAL_DATE.format(buildTimeAndDate)
    buildTime = DateTimeFormatter.ofPattern('HH:mm:ss.SSSZ').format(buildTimeAndDate)
}


// Configuration for all projects, including root projects
allprojects {
    group 'com.bolingcavalry'
    version '1.0-SNAPSHOT'

    apply plugin: 'java'
    apply plugin: 'idea'
    apply plugin: 'io.spring.dependency-management'

    // Make a closure for setting parameters, which will be used later by both compileJava and compileTestJava
    def compileSetUp = {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
        options.encoding = 'UTF-8'
        options.compilerArgs =  [
                '-Xlint:all', '-Xlint:-processing'
        ]
    }

    compileJava (compileSetUp)

    compileTestJava (compileSetUp)

    // Copy LICENSE
    tasks.withType(Jar) {
        from(project.rootDir) {
            include 'LICENSE'
            into 'META-INF'
        }
    }

    // When generating the jar file, the contents of MANIFEST.MF are as follows
    jar {
        manifest {
            attributes(
                    'Created-By': "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})".toString(),
                    'Built-By': 'travis',
                    'Build-Date': buildDate,
                    'Build-Time': buildTime,
                    'Built-OS': "${System.properties['os.name']}",
                    'Specification-Title': project.name,
                    'Specification-Version': project.version,
                    'Specification-Vendor': 'Will Zhao',
                    'Implementation-Title': project.name,
                    'Implementation-Version': project.version,
                    'Implementation-Vendor': 'Will Zhao'
            )
        }
    }

    // Warehouse
    repositories {
        // local
        mavenLocal()
        // Central warehouse
        mavenCentral()
        // Grand plugin
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
}

// Similar to maven's dependency management, the versions of all jar s are specified here, and the sub modules do not need to specify the version when relying
allprojects { project ->
    buildscript {
        dependencyManagement {
            imports {
                mavenBom "org.springframework.boot:spring-boot-starter-parent:${springBootVersion}"
                mavenBom "org.junit:junit-bom:5.7.0"
            }

            dependencies {
                dependency 'org.projectlombok:lombok:1.16.16'
                dependency 'org.apache.commons:commons-lang3:3.11'
                dependency 'commons-collections:commons-collections:3.2.2'
                dependency 'org.slf4j:slf4j-log4j12:1.7.30'
            }
        }
    }
}

// Coordinate information
group 'com.bolingcavalry'
version '1.0-SNAPSHOT'
  1. Create a new module named democlient as a second-party library. When providing services, the data structure and interface are placed here. The content of build.gradle is as follows:
plugins {
    id 'java-library'
}

// Sub module's own dependency
dependencies {
    api 'org.projectlombok:lombok'
    // The annotation processor will not be passed. Modules that use lombok generated code need to declare the annotation processor themselves
    annotationProcessor 'org.projectlombok:lombok'
    // slf4j's package can only be used by itself. Do not inherit it to other projects, otherwise it is easy to conflict with other log packages
    implementation 'org.slf4j:slf4j-log4j12'
    testImplementation('org.junit.jupiter:junit-jupiter')
}

test {
    useJUnitPlatform()
}
  1. Create a new module named demowebapp, which is a springboot application. Its build.gradle content is as follows:
plugins {
    id 'org.springframework.boot'
}

// After using the plug-in org.springframework.boot, the jar task will fail and can be replaced by bootJar
bootJar {
    archiveBaseName = project.name
    archiveVersion = project.version

    manifest {
        attributes(
                'Created-By': "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})".toString(),
                'Built-By': 'travis',
                'Build-Date': buildDate,
                'Build-Time': buildTime,
                'Built-OS': "${System.properties['os.name']}",
                'Specification-Title': project.name,
                'Specification-Version': projectVersion,
                'Specification-Vendor': 'Will Zhao',
                'Implementation-Title': project.name,
                'Implementation-Version': projectVersion,
                'Implementation-Vendor': 'Will Zhao'
        )
    }
}

// Sub module's own dependency
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    // Binary library dependency
    implementation project(':democlient')
    // The annotation processor will not be passed. Modules that use lombok generated code need to declare the annotation processor themselves
    annotationProcessor 'org.projectlombok:lombok'
    implementation 'commons-collections:commons-collections'
    implementation 'org.apache.commons:commons-lang3'
    testImplementation('org.junit.jupiter:junit-jupiter')
}

test {
    useJUnitPlatform()
}
  1. The startup class in the demowebapp module is DemoWebAppApplication.java:
package com.bolingcavalry;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoWebAppApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoWebAppApplication.class, args);
    }
}
  1. Add a controller class to verify whether the service is normal:
package com.bolingcavalry.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;

@RestController
@Slf4j
public class Hello {

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello() {
        log.info("execute hello");
        return "hello " + new Date();
    }
}
  • After the project is created, it can be seen that this is a very simple and typical spring boot project with parent-child structure;

Build image practice

  1. Create a Dockerfile in the demowebapp directory. It can be seen that it is very simple. It only specifies the account and group, and the files required to copy the image:
FROM openjdk:8-jdk-alpine
# Add groups and users
RUN addgroup -S spring && adduser -S spring -G spring
# Specify the users and groups of process 1 when the container runs
USER spring:spring
# Specifies the source location of the mirrored content
ARG DEPENDENCY=build/dependency
# Copy content to mirror
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
# Specify start command
ENTRYPOINT ["java","-cp","app:app/lib/*","com.bolingcavalry.DemoWebAppApplication"]
  1. After preparation, you can start to create the image. The first step is to compile and build the whole project. Execute the following command in the Java demo directory to compile and build the project:
chmod +x gradlew && ./gradlew build
  1. After successful programming, you need to extract the contents of the jar (that is, those files required by the COPY command in Dockerfile):
mkdir -p demowebapp/build/dependency \
&& (cd demowebapp/build/dependency; jar -xf ../libs/*.jar)
  1. Go to demowebapp/build/dependency. The content is ready:
  1. Execute the following command to build an image. The tag of the image is the current month, day, hour, minute and second:
cd demowebapp \
&& docker build \
-t bolingcavalry/demowebapp-docker:`date "+%Y%m%d%H%M%S"` .
  1. After the execution is completed, the console prompts as follows:
(base) zhaoqindeMBP:java-demo zhaoqin$ cd demowebapp \
> && docker build \
> -t bolingcavalry/demowebapp-docker:`date "+%Y%m%d%H%M%S"` .
[+] Building 1.7s (10/10) FINISHED
 => [internal] load build definition from Dockerfile                                                                  0.1s
 => => transferring dockerfile: 542B                                                                                  0.0s
 => [internal] load .dockerignore                                                                                     0.0s
 => => transferring context: 2B                                                                                       0.0s
 => [internal] load metadata for docker.io/library/openjdk:8-jdk-alpine                                               0.0s
 => [1/5] FROM docker.io/library/openjdk:8-jdk-alpine                                                                 0.1s
 => [internal] load build context                                                                                     0.8s
 => => transferring context: 20.05MB                                                                                  0.7s
 => [2/5] RUN addgroup -S spring && adduser -S spring -G spring                                                       1.1s
 => [3/5] COPY build/dependency/BOOT-INF/lib /app/lib                                                                 0.1s
 => [4/5] COPY build/dependency/META-INF /app/META-INF                                                                0.0s
 => [5/5] COPY build/dependency/BOOT-INF/classes /app                                                                 0.0s
 => exporting to image                                                                                                0.1s
 => => exporting layers                                                                                               0.1s
 => => writing image sha256:765d7a01490aaf2bd301e1cee68b3dc90b22768a5e7c957cde280cc2215d2848                          0.0s
 => => naming to docker.io/bolingcavalry/demowebapp-docker:20210406080915
  1. Check the local image. The newly created image is shown in the red box below:

verification

  • Execute the following command to start the image (please modify the image name according to your actual situation):
docker run --rm -p 8080:8080 bolingcavalry/demowebapp-docker:20210406080915
  • The browser accesses the address Java demo, as shown in the following figure, which can work normally:

reference material:

Official documentation: https://spring.io/guides/gs/spring-boot-docker/

  • So far, the operation of making the springboot application built by gradle into a docker image has been completed. If you are making your own application into a docker image, I hope this article can give you some reference;

Posted on Tue, 07 Dec 2021 03:26:54 -0500 by ctsttom