In our previous article, we learned how to dockerize your Java Springbot project using Jib and Gradle plugin. But in that we have to manually build the docker image and push it to the docker hub. In this article we are going to learn how to automate this manual process using GitHub action.

Prerequisites

  • A GitHub repository containing your Java project with Gradle as the build tool
  • A Docker Hub account
  • Initalize the GitHub Action Workflow

    Naviagte to your local project directory and create a new directory called .github/workflows . Inside that directory create a new file called action.yml. This file will contain the workflow configuration. First we need to provide name for our workflow and need to define the event that will trigger the workflow. In this case we are going to use push event. So our workflow configuration will look like this.

    1
    2
    3
    4
    5
    6
    name: Java CI with Gradle // name of the workflow

    on:
    push: // event that will trigger the workflow
    branches:
    - master // branch that will trigger the workflow

    Now we need to define the jobs that will run when the workflow is triggered. In this case we are going to use job called deploy but you can use any other name you wish to use and it’s running on ubuntu-latest virtual machine. So our workflow configuration will look like this.

    1
    2
    3
    4
    ...
    jobs:
    deploy: // job that will run when the workflow is triggered
    runs-on: ubuntu-latest // virtual machine that will run the job

    In next step we’ll moving to steps section. In this section we are going to define the steps that we need to build and push Docker image.

    Checkout code

    First we need to checkout the code from the repository. To do that we need to add the following code to our workflow configuration.

    1
    2
    3
    4
    5
    6
    ...
    jobs:
    deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    Setup Java

    Before we build the Docker image, we need to run tests and verify that our application is working properly. To do that we need to create Java env within our workflow. To do that we need to setup Java. To do that we need to add the following code to our workflow configuration.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ...
    jobs:
    deploy:
    runs-on: ubuntu-latest
    steps:
    ...
    - name: Set up JDK 11
    uses: actions/setup-java@v3
    with:
    java-version: '11' // version of the java (you can use any version you wish to use)
    distribution: 'temurin' // distribution of the java (you can use any distribution you wish to use)

    Make Gradle executable

    In certian cases there might be an error saying gradlew is not executable. To avoid that we need to add the following code to our workflow configuration.

    1
    2
    3
    4
    5
    6
    7
    8
    ...
    jobs:
    deploy:
    runs-on: ubuntu-latest
    steps:
    ...
    - name: Make gradlew executable
    run: chmod +x ./gradlew

    Setup Gradle Environment

    As I previsouly mentioned we have used Gradle as our build tool. So we need to setup Gradle environment. To do that we need to add the following code to our workflow configuration.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ...
    jobs:
    deploy:
    runs-on: ubuntu-latest
    steps:
    ...
    - name: Setup Gradle
    uses: gradle/gradle-build-action@v2
    with:
    cache-disabled: true // by default gradle cache is enabled. (refer gradle/gradle-build-action@v2 for more information)

    Now we have setup both Java and Gradle environments within our workflow. Next we need to build our project to check whether it’s working or not.

    Build the project

    Now we are going to build our project. This step helps us to verify that whether there's an any error in our application or not. To do that we need to add the following code to our workflow configuration.

    1
    2
    3
    4
    5
    6
    7
    8
    ...
    jobs:
    deploy:
    runs-on: ubuntu-latest
    steps:
    ...
    - name: Execute Gradle build
    run: ./gradlew --no-daemon clean build

    In here we have used –no-daemon flag to avoid Gradle daemon to avoid long running process and to isolate the each build. clean is used to clean the previous build and build task is a built-in task that compiles the source code, runs tests, and creates build artifacts. It is typically the primary task used to generate the final output of the project, such as JAR files, test reports, and other build artifacts. By using this step we can check whether our project is building successfully or not.

    Build the Docker image

    Now we are going to build our Docker image. To do that we need to add the following code to our workflow configuration.

    1
    2
    3
    4
    5
    6
    7
    8
    ...
    jobs:
    deploy:
    runs-on: ubuntu-latest
    steps:
    ...
    - name: Build docker image
    run: ./gradlew --no-daemon jibDockerBuild

    To use this step we need to add some custom configuration to our project. First we need to add Jib to our project. To do that we need to add the following code to our build.gradle file’s plugin section.

    1
    2
    3
    4
    plugins {
    ...
    id 'com.google.cloud.tools.jib' version "${jibVersion}" // jibVersion is a variable that we have defined in gradle.properties file https://github.com/dinushchathurya/springboot-gradle-jib-cicd/blob/master/gradle.properties
    }

    Then we need to add the following code to our build.gradle file’s jib section.

    1
    2
    3
    4
    5
    6
    7
    8
    jib {
    from {
    image = "gcr.io/distroless/java@sha256:629d4fdc17eec821242d45497abcb88cc0442c47fd5748baa79d88dde7da3e2d"
    }
    to {
    image = "<docker-repository>/${project.name}:${project.version}"
    }
    }

    To generate unique and standard docker image tag, here I have used custom task called generateMetadata. To do that we need to add the following code to our build.gradle file’s task section.

    1
    2
    3
    4
    task generateMetadata(type: WriteProperties) {
    property("version", project.version)
    setOutputFile("${buildDir}/metadata.properties")
    }

    And make sure to add the following code to our build.gradle file. This allows to sets up a version number for a multi-project build.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    allprojects {
    repositories {
    maven {
    url 'https://jitpack.io'
    }
    }

    ext {
    buildNumber = System.getenv("GITHUB_RUN_NUMBER") ?: "0"
    }

    version = "${project.majorVersion}-build-${project.ext.buildNumber}".toString()
    }

    We need to import apache ant filters ReplaceTokens library in our build.gradle file. To do that we need to add the following code to our build.gradle file’s import section.

    1
    import org.apache.tools.ant.filters.ReplaceTokens

    The ReplaceTokens class is a built-in filter class provided by Apache Ant, which is used to perform token replacement in text files. In Ant build scripts, tokens are placeholders surrounded by ${} or @{}. These tokens can be replaced with specific values during the build process.

    Push the Docker image

    Now we are going to push our Docker image to the Docker Hub. To do that we need to add the following code to our workflow configuration.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ...
    jobs:
    deploy:
    runs-on: ubuntu-latest
    steps:
    ...
    - name: Docker login
    uses: docker/login-action@v2 //login to the docker hub
    with:
    username: ${{ secrets.DOCKER_USERNAME }} // you need to add your docker username to the github secrets
    password: ${{ secrets.DOCKER_PASSWORD }} // you need to add your docker password to the github secrets
    - name: Push docker image // push the docker image to the docker hub
    run: ./gradlew --no-daemon dockerPush

    To use this step we need to create a custom task in our build.gradle file. To do that we need to add the following code to our build.gradle file’s task section.

    
    task dockerPush {
        doLast {
            exec {
              commandLine 'docker', 'push', "<docker-repository>/${project.name}:${project.version}"
            }
        }
    } 
    

    Complete workflow configuration

    Now we have finihsed with our entire workflow part. You can find the complete workflow configuration from here.

    Conclusion

    In this article, we learned how to automate your Java Springbot project building using Jib, Gradle plugin and GitHub Actions. You can find the all the related commands for this tutorial from here. If you have any issue regarding this tutorial, mention your issue in the comment section or reach me through my E-mail.

    Happy Coding