Build a Docker image with Gradle

Mondo Technology Updated on 2024-02-01

Based on Gradle, you can generate a Docker image without installing Docker Toolbox.

Docker is an open-source LXC-based lightweight container manager, with the help of Docker, developers only need to package the application and the dependent runtime environment into a portable container, and they can run normally, regardless of the limitations of language, framework and underlying operating system, using Docker can shorten development time, exploration time and deployment cycle, so that they can code more productively, simplify the development environment, eliminate dependency conflicts, etc., and ultimately increase development productivity.

However, when the Windows platform uses Docker technology, it is necessary to install and configure additional Docker tools, which will inevitably take extra work time, and if you want to configure a set of build environments consistent with the production environment in the Dev environment, it will require a lot of effort.

In order to speed up development and build and significantly improve development efficiency in development scenarios, it is recommended to use Gradle to build, and use Gradle scripts to efficiently and conveniently manage differences, dependencies, compilation, packaging, and deployment processes in a project. This article will focus on the use of Gradle and how to create Docker images with Gradle, how to leverage Gradle scripts and how to build Docker images with Central Docker Engine from a developer perspective.

In the traditional build system, ant and m**en will be used as build tools, and the recommended one here is gradle, which does not use xml as a configuration file, but uses DSL developed based on groovy, so the configuration file of gradle is more efficient and concise than that of ant and m**en. The following are the main differences between Gradle and Ant and M**En

ANT is the earliest build tool, and its core is written in J**A, using XML as the build script, because it is based on J**A, which allows the build to be run in any environment. ANT is based on the idea of task chains, which define dependencies between tasks and form a sequence. The disadvantage is that the use of XML to define build scripts leads to bloated scripts, and ANT itself does not provide guidance for project builds, resulting in different build scripts, and developers need to be familiar with the script content for each project, and do not provide dependency management tools in the ANT ecosystem. M**en is aware of the shortcomings of ANTU, adopts a standard project layout, a unified lifecycle and strong dependency management, uses the idea of "convention is better than configuration", reduces the configuration of build scripts, improves development efficiency and management order, and has an active community that can be easily extended with suitable plugins. The disadvantages are that it has a default structure and lifecycle, has a lot of limitations, is cumbersome to write plugin extensions, and uses xml as a build script that makes the build script verbose. Gradle has the advantages of both ANT and M**EN, has the power and flexibility of ANT, and inherits the lifecycle management of M**EN. It uses Groovy-based DSL and provides a declarative build language, which is simple and straightforward, making Gradle build scripts more concise and clear, and using a standard project layout with full configurability. You can also extend the plug-in to provide a default build lifecycle, customize tasks, run tasks separately, and define dependencies between tasks. Gradle inherits and improves the dependency management of m**en, which can be combined with m**en and ivy repositories; At the same time, it is inherently compatible with ant, and effectively reuses ant's tasks; It has a variety of ways to implement plug-ins, and has a strong official plug-in library; From the build level, it supports gradual migration from ant and m**en; Seamless compatibility across platforms through wrappers. JDK: The latest J**A8 version, and install and configure environment variables. gradle build tool: Please go to the latest complete version. Eclipse: Eclipse IDE for j**a EE Developers version, please go to *** latest version. The core of Gradle is Groovy, and the essence of Groovy is J**A, so it is obvious that the Gradle environment must rely on JDK and Groovy libraries, so you need to install JDK and configure environment variables first.

* gradle-4.5-all, select the latest version from Gralde, then unzip to the specified directory, and add the bin directory of gradle to the path variable to configure the environment variable. Check whether the gradle environment variable is configured. Enter gradle -v on the cmd command line, and if a message similar to the following message appears, the gradle environment is successfully configured.

---gradle 4.5---build time: 2018-01-24 17:04:52 utcrevision: 77d0ec90636f43669dc794ca17ef80dd65457becgroovy: 2.4.12ant: apache ant(tm) version 1.9.9 compiled on february 2 2017jvm: 1.8.0_151 (ibm corporation 2.9)os: windows 7 6.1 amd64
Note: The above information may be different depending on the Gradle version or different environments.

Create a new root directory named gradle project, which uses multiple sub-projects as an example, so you also need to create a subdirectory under the gradle project directory

gradle_project_util;gradle_project_model;gradle_project_web;
Create a buildgradle file with the following contents:

Listing 1Root project gradle configuration.

subprojects jcenter } dependencies {}version = '1.0' jar }
Create settingsgradle file, settingsThe gradle file is used to manage the entire project, declaring what modules are in the current project, etc. The details are as follows:

Listing 2Root project settings configuration.

rootproject.name = "gradle_project"include "gradle_project_util","gradle_project_model","gradle_project_web"
After the creation is completed, the following figure is displayed:

Figure 1An example of a directory structure.

Create a build. in a subprojectgradle file and add something like this: Listing 3Subproject gradle scripts.

apply plugin: 'j**a' repositories dependencies /***project template***/ task createdirs sourcesets*.resources.srcdirs*.each }
Execute the :gradle createdirs command line on the cmd command line, and the standard directory structure of the j**a project will be created after the command is executed.

Gradle follows the COC (Convention over Configuration) philosophy and provides the same project structure configuration as M**en by default. The general structure is as follows:

sub-project root

src main j**a (project directory)src main resources (project resource directory)src test j**a (test source directory)src test resources (test resource directory) to import gradle project into eclipse: select file->import->gradle in the eclipse menu, as shown in the following figure:

Figure 2Example of project import.

After selecting next, select the directory where the gradle project is located, and then continue to select the default value to continue next until the project structure resolution is completed, as shown in the following figure:

Figure 3Gradle Project Structure example.

Once the import is complete, the Project Structure displayed in Project Explore in Eclipse is shown in the following image, and the project structure that is consistent with M**en has been created.

Figure 4Project Explore example.

The essence of gradle-based script is groovy script, when a type of configuration script is executed, an associated object will be created, for example, a project object will be created when the build script script is executed, and this object is actually a gradle object.

The three main objects of Gradle are explained below:

gradle object: This is the only object created during the initialization of the build, and it is generally not recommended to modify this default configuration. settings object: per settingsgradle(gradle's settings file with a convention name defined by settings.)gradle) will be converted to a settings object, which is executed during the initialization phase, and for multi-project builds, you must ensure that there are settingsgradle file, for single-project build settings files is optional, but it is recommended that you still need to configure it. Project object: Each buildGradle converts to a Project object. The three different epoch scripts of Gradle are used for the following:

Initialize the script init script(gradle).

Gradle Object: The initialization script Init Script (Gradle) is similar to other types of Gradle scripts in that they run before the build starts, and is primarily used to prepare for the next build script. If we need to write an initialization script, init script, we can place it in the user home according to the rulesgradle.

The gradle object of the initialization script represents the dispatch of gradle, and we can get the gradle instance object by calling the getgradle() method of the project object.

Set the script settings script(settings).

Settings object: A settings instance and a settingsThe gradle file is a one-to-one correspondence and is used to configure some project settings. This file is usually placed in the root directory of the project.

Build script(project).

In Gradle, each project to be compiled is a project (each project's buildgradle corresponds to a project object), and each project is built with a series of tasks, many of which are supported by the gradle plugin by default. When we write gradle scripts, we are essentially writing build scripts most of the time, so APIs such as properties and methods of project and script objects are very important.

Every project object and buildgradle is a one-to-one correspondence, and a project has the following process when it is built:

Create an instance of type settings for the current project. If there are settings in the current projectgradle file, use this file to configure the settings instance you just created. Create an instance of the project object in the project hierarchy through the configuration of the settings instance. Finally, the project object instance created above executes the build. for each projectgradle scripts. As a developer of building scripts, you should not only be limited to writing task actions or configuration logic, sometimes you want to execute a ** when a specified lifecycle event occurs, here you need to understand the lifecycle event. Lifecycle events can occur before, during, or after a specified lifecycle, and a lifecycle event that occurs after the execution phase is considered complete. The specific implementation cycle is shown in the figure below

Figure 5Gradle Life Cycle Diagram.

Initialization phase: Gradle supports single-project and multi-project builds, in the initialization phase, gradle decides which projects need to be added to the build, and creates project instances for these projects that need to be added to the build, which is essentially to execute settingsgradple script.

configuration phase: The configuration phase determines the relationship between the project and the task of the entire build, and it will establish a directed graph to describe the interdependencies between the tasks, and resolve the build. of each build that is added to the build projectgradle scripts.

Execution phase: The execution phase is the generation of gradle to execute the custom tasks under the root-project and each sub-project and the tasks it depends on to achieve the final build goal.

As you can see, the life cycle can actually be related to the execution process of the build script above.

In multi-project construction, you need to specify a tree root, each node in the tree represents a project, and each project object is specified with a path that represents the location in the tree. The general practice is to make sure that there are settingsThe gradle file was defined when the gradle project was originally created, so it will be expanded later on for the build script file.

As mentioned earlier, the gradle build script is composed of a series of tasks, and it is necessary to understand how to write tasks first

Listing 4There are and no action task scripts.

TaskTask Hello } Task without Action, this is a shortcut, replace Dolast, explain Task Hello << later
In the above example, if the task is not executed with "", the task is executed during the initialization phase of the script (i.e., whatever task is executed), and if it is executed, it will be executed after the gradle actiontask. Because if you don't add "", the closure will be executed before the task function returns, and if you add "", it will call actiontaskdolast(), so it will wait until gradle actiontask is executed, which must be remembered, because in the specific use process, you will find that the same task may not be executed in the order of task dependencies, and you should consider whether it is affected by this writing.

Through the above example of the basic feeling of the task, it can be found that a buildThe gradle file defines multiple tasks that have nothing to do with each other, and the task name followed by the gradle command is determined. What if we want them to have dependencies between them, what to do first and what to do later? Let's look at the following:

Listing 5task depends on scripts.

task taskx(dependson: 'tasky')$ gradle taskx> task :taskytasky> task :taskxtaskxbuild successful in 0s2 actionable tasks: 2 executed
This is how to use dependson to connect two separate tasks to each other.

You can also use Groovy in Gradle to create dynamic tasks, as follows:

Listing 6Create a dynamic task script.

4.times }
The result is as follows:

$ gradle task1> task :task1i'm task number 1build successful in 0s1 actionable task: 1 executed
In addition to specifying the dependencies when defining the task above, you can also add a dependency to the task via the API, as follows:

Listing 7task indicates the dependent script.

4.times }task0.dependson task1, task1
The result is as follows:

$ gradle task0> task :task1i'm task number 1> task :task2i'm task number 2> task :task0i'm task number 0build successful in 0s3 actionable tasks: 3 executed
You can also add some new behaviors to your tasks through the API, such as:

Listing 8task dofirst, dolast scripts.

task hello <$gradle hello> task :hellohello venushello earthhello marshello jupiterbuild successful in 0s1 actionable task: 1 executed
As you can see, dofirst and dolast can be executed multiple times," the operator is essentially dolast.

You can also use a dollar symbol as a property of another task as follows:

Listing 9task reference script.

task hello <$gradle hello> task :hellohello world!greetings from the hello task.build successful in 0s1 actionable task: 1 executed
The name used in the script above is actually the default property of the task, which represents the name of the current task.

You can also add custom attributes to the task, as shown in the following example:

Listing 10task custom attribute scripts.

task task1 task printtaskproperties <<
The result is as follows:

$ gradle printtaskproperties> task :printtaskpropertiesvaluebuild successful in 0s1 actionable task: 1 executed
In addition, gradle allows you to define one or more default tasks in your script, as follows:

Listing 11task custom task scripts.

defaulttasks 'clean', 'run'task clean <$gradle> task :cleandefault cleaning!> task :rundefault running!build successful in 0s2 actionable tasks: 2 executed
As mentioned earlier, gradle defines a project in the build script, and for each sub-project in the build script, gradle creates a project object to associate with, and when the build script is executed, it will configure the associated project object; Each method and property that is called in the build script is delegated to the current Project object.

The project object provides some standard properties that are convenient to use in build scripts.

For more information on the project-specific approach, see the project's documentation. For J**A projects, you generally need to clean the previous build results, start the build, copy the build results to the specified directory, copy other required configurations, scripts, etc. to the specified directory before the build starts, and the above steps are all preparatory steps for building a Docker image. The details are as follows:

Listing 12gradle build scripts.

apply plugin: 'j**a' repositories dependencies task cleandocker(type: delete) /docker") task copybuild(type: copy, dependson: build) -jar" into 'build/docker' } task copyscript(type: copy, dependson: copybuild) /resources/main/script") into project.file("$/docker/script")}
After the execution is complete, the result is as follows:

$ gradle copyscriptbuild successful in 1s5 actionable tasks: 5 executed
At the same time, you can find that there are build generated files, copied configuration files, and script files under the project build directory $projectdir build, as shown in the following figure

Figure 6Build a result graph.

In the J**A project, some common ** is referenced by multiple projects at the same time, for such a part of the code needs to be mentioned in the common project, and compiled into a jar package and published in the repository, so that the project that needs to use common ** only needs to download the latest version of the jar package in the repository according to the jar package information.

Typically, you can add project dependencies as follows:

dependencies
In gradle, dependencies can be combined into configurations, a configuration is simply a series of dependencies, commonly understood as dependency configurations, which can be used to declare external dependencies of the project, and can also be used to declare the release of the project. Below we give a few common configurations in j**a plugins, as follows:

compile: used to compile dependencies on the source of the project; runtime: the dependencies required by the class generated at runtime, the default items, including the dependencies at compile time; testcompile: compile test dependencies, default items, containing the dependencies required for the run of the generated class and the dependencies of the compilation source; testRuntime: the dependencies required to run the test, the default item, including the above three dependencies; Gradle can declare a number of dependencies, one of which is an external dependency, which is a dependency outside of the current build, usually stored in a remote (e.g. m**en) or local repository. Here's an example of an external dependency:

dependencies
As you can see, referencing an external dependency requires the group, name, and version attributes. The above is two different ways of writing an external dependency, the second of which is shorthand.

According to the j**a specification for publishing jar packages, generally publishing jar packages also needs to be accompanied by a source** for users to refer to and use. Therefore, when it is packaged into a jar, the source code will also be packaged, as follows**:

Listing 13jar packaging script.

task sourcejar(type: jar)
In addition, you also need to configure the upload jar package and repository information

Listing 14jar release script.

publishing }artifactory defaults }}
The configuration here is: the URL address, user name, password and other information of the remote repository, and according to the above gradle script, the ** of the common project will be published for use by other projects.

Similar to the use of war packages to publish J**A applications in the past, most of the current microservices and web applications are published using docker images, and the convenience of docker image release has been introduced at the beginning of this article, so I will not expand on it in depth here.

In this section, we will introduce how to ensure that the Docker image released for testing by each developer is consistent with that released in the production environment. The concept of Central Docker Engine is introduced here to ensure that every developer builds a Docker Image through Central Engine, without requiring each developer to configure Docker Engine in the development environment, so that the differences in the Docker Build environment caused by different Docker Toolbox versions and different OS can be eliminated. The concept of Central Docker Engine is shown in the following diagram:

Figure 7Central Docker Engine example diagram.

The centralized build requires a Central Docker Engine, and the configuration information is based on CentOS 73. Other operating systems can also be configured based on the above concepts. The main idea of the configuration is to install Docker Engine on Linux Server, configure port 2375 TCP in Docker Engine, and release this port in Firewall so that other services can connect to build remotely. The specific steps are as follows:

Use the following command to install docker and docker engine:

sudo yum install docker-io

sudo yum install docker-engine

After the above command is installed, you also need to configure docker.

Modify lib systemd system dockerThe following two lines in the service are:

environmentfile=-/etc/sysconfig/docker

execstart=/usr/bin/dockerd $docker_opts

Modify the contents of etc sysconfig docker to the following value:

docker_opts=’-h tcp: -h unix:///var/run/docker.sock’
Firewall releases port 2375.

firewall-cmd –zone=public –add-port=2375/tcp –permanent

firewall-cmd –reload

After the above configuration is completed, the Central Docker Engine has been set up.

Since the main use of plugin:combmuschko.docker-remote-api to complete the call to the remote docker engine, and the gradle plugin will be introduced here.

In fact, the core of gradle is mainly a framework, the so-called gradle is easy and fast to build, in fact, it is supported by a series of plugins, plugins add new tasks. There are generally two types of plugins in Gradle, as follows:

Script plugins are additional build scripts that further configure the build and are usually used inside the build. Script plug-ins can be fetched from the local file system or remotely, relative to the project directory if fetched from the file system, or specified by the http url if fetched remotely.

Binary plugins are classes that implement the plugin interface and programmatically provide some useful tasks for the build.

Referencing the plugin needs to be done via projectapply() method to complete the declaration application, the same plugin can be applied multiple times. Here's an example:

Script plugin apply from:'build.gradle'Binary plugin apply plugin:'j**a'
Plug-ins can also use plug-in IDs, which are used as unique identifiers for a given plug-in, and can be registered with an abbreviated character ID for later use. For example, here's an example:

Referenced by the id of the j**a plugin. 

apply plugin: codecheck

In the previous section, we talked about how to use the Gradle plugin, and in this section, we will use the Gradle plugin to build a Docker image, which relies on plugin:combmuschko.docker-remote-api completes the remote build and publish of docker images.

Before building a Docker Image, you need to use a gradle script to complete a series of preparations, including copying the configuration file and startup script to a specified directory, building the target jar package, and then using the gradle plugin to generate a dockerfile, and then publishing based on the dockerfile information.

Configure Docker Engine information.

Listing 15Docker Engine information configuration script.

docker else registrycredentials }
The above script will use a different URL depending on whether it is a Windows platform or a Linux platform, which directly uses DockerSock, Windows platforms need to connect to Docker Engine through the TCP port.

Preparation, including clearing legacy of previous builds and copying the compilation results to the target directory.

Listing 16docker pre-preparation script.

task cleandocker(type: delete) /docker") }task copybuild(type: copy, dependson: build) -jar" into 'build/docker'}task copyscript(type: copy, dependson: copybuild) /resources/main/script") into project.file("$/docker/script")}
Generate a dockerfile

Listing 17Generate a dockerfile script.

task createdockerfile(type: dockerfile, dependson: copyscript) -jar", docker_work_home addfile 'script', "$/script" environmentvariable 'j**a_opts' , j**a_opts environmentvariable 'boot_target' , "$-$jar" runcommand "chmod +x /home/root/script/*.sh" entrypoint("sh", "-c", /home/root /script/startup.sh")}
build docker image, tag, publish

Listing 18Publish the docker image script.

task builddockerimage(type: dockerbuildimage, dependson: createdockerfile) task builddockertagimage(type: dockertagimage, dependson: builddockerimage) repository = docker_image conventionmapping.tag = force = true}task pushimage(type: dockerpushimage, dependson: builddockertagimage) conventionmapping.tag = } artifactorypublish
Finally, run the command: gradle artifactorypublish in cmd to complete the docker image build and publish.

For the team collaboration environment, instead of building a separate Docker environment for each developer, it is better to build a set of Central Docker Engine environment that is consistent with the production environment, which is not only convenient for maintenance, but also ensures that the Docker image generated by the developer is consistent with the Docker image generated by the production environment. The above is just a little personal thinking in the actual project, if there are deficiencies, I hope that readers can be able to Haihan, if so, I hope that readers can give feedback, exchange experiences, and make progress together.

Related Pages