Bhuwan Upadhyay

Talks all about software engineering

Published on

Semantic versioning on docker build and helm chart



Helm best practice guide advocate semantic versioning for the helm chart that your release for deployment. Wherever possible, Helm uses SemVer 2 to represent version numbers. Semantic versioning is a meaningful method for incrementing version numbers. So, today we will explore how to release helm charts and docker build by using semantic versioning convention.

In this example, I will release semantic versions for helm chart and docker image of spring boot microservice that build upon maven.

Docker build and Helm chart for spring boot

Let's start with spring boot a simple microservice that exposes API to return a given name.

Initialize Project

NAME='Semantic versioning on docker build and helm chart' && PRJ=semantic-versioning-on-docker-build-and-helm-chart && \
mkdir -p $PRJ && cd $PRJ && \
curl https://start.spring.io/starter.tgz \
    -d dependencies=actuator,webflux \
    -d groupId=io.github.bhuwanupadhyay -d artifactId=$PRJ -d packageName=io.github.bhuwanupadhyay.example \
    -d applicationName=Spring Boot -d name=$NAME -d description=$NAME \
    -d language=kotlin -d platformVersion=2.3.1.RELEASE -d javaVersion=11 \
    -o demo.tgz && \
    tar -xzvf demo.tgz && rm -rf demo.tgz

Create API to return given name

class NameRoutes(private val handler: NameHandler) {

    fun router() = router {
        accept(APPLICATION_JSON).nest {
            GET("/names/{given-name}", handler::findGivenName)

Dockerfile for spring boot src/main/docker/Dockerfile

From Spring Boot 2.3.0.RELEASE they introduced layertools to create optimized Docker images that can be built with a dockerfile.

FROM adoptopenjdk:11.0.7_10-jre-hotspot as builder
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM adoptopenjdk:11.0.7_10-jre-hotspot
COPY --from=builder app/dependencies/ ./
COPY --from=builder app/spring-boot-loader/ ./
COPY --from=builder app/snapshot-dependencies/ ./
COPY --from=builder app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

Helm chart for spring boot src/main/helm/my-service

Simply run the create helm command.

mkdir -p src/main/helm && helm create src/main/helm/my-service

In your helm chart under src/main/helm/my-service/templates/deployment.yaml change readinessProbe and livenessProbe health check settings, also modify container port to 8080 is the default for spring boot application.

  - name: http
    containerPort: 8080
    protocol: TCP
    path: /actuator/health
    port: http
  initialDelaySeconds: 30
  periodSeconds: 60
  timeoutSeconds: 5
  failureThreshold: 5
    path: /actuator/health
    port: http
  initialDelaySeconds: 30
  periodSeconds: 5
  timeoutSeconds: 5
  failureThreshold: 5

Also, replace value image repository with your published docker image name without a tag in src/main/helm/my-service/values.yaml inside a helm chart.

  repository: docker.io/bhuwanupadhyay/my-service
  pullPolicy: IfNotPresent
  tag: ''

In src/main/helm/my-service/Chart.yaml there are two properties:

  • version [chart version] - Versions are expected to follow Semantic Versioning (https://semver.org/) This is the chart version. This version number should be incremented each time you make changes.
  • appVersion [default value for image tag] - Versions are expected to follow Semantic Versioning (https://semver.org/) This is the version number of the application being deployed. This version number should be incremented each time you make changes to the application. In maven pom.xml, the flatten-maven-plugin to set revision number for project, which will use by dockerfile-maven-plugin to build the docker image repository/image-name:<revision> with revision and helm-maven-plugin create package with that revision for chart version and appVersion. It's very important to provide consistent releases during the life cycle of the product. To achieve this we will use very popular tool Semantic Release with Conventional Commits.

Semantic Release Process


Github Pipeline

Let's create semantic-release configuration .releaserc file in your project directory. In this configuration, we have two commands given by @semantic-release/exec plugin that we will use to a build the release and publish helm package on GitHub Packages and docker image on docker.io.

  • prepareCmd: The shell command to execute during the prepare step.
  • publishCmd: The shell command to execute during the publish step.
  "branches": ["master"],
  "plugins": [
        "prepareCmd": "./bot.sh --prepare ${nextRelease.version}",
        "publishCmd": "./bot.sh --publish ${nextRelease.version}"

Here bot.sh is a script file used to build and publish the docker image and helm chart package using the next version given by a semantic release process.

set -e
case ${option} in
      ./mvnw \
        -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -V \
        clean install -Drevision="$next_version"
      # Publish Docker in Github Packages
      docker login docker.pkg.github.com -u BhuwanUpadhyay -p "$GITHUB_TOKEN"
      docker tag docker.io/bhuwanupadhyay/my-service:"$next_version" "$DOCKER_PKG"
      docker push "$DOCKER_PKG"
      # Publish Helm chart in Github Releases
      # TODO: yourself
      # Write a suitable script to upload your helm chart in your Artifactory or chart museum.
      echo "Mock publish: $HELM_CHART from $HELM_CHART_FILE_PATH"
      echo "`basename ${0}`:usage: [--prepare] | [--publish]"
      exit 1 # Command to come out of the program with status 1

Finally, we need a workflow action YAML configuration to run the Github pipeline under .github/workflows/build.yml.

name: Java CI
      - '*'
    runs-on: ubuntu-latest
    name: Build
      - uses: actions/checkout@v2
      - name: Set up JDK 11
        uses: actions/setup-java@v1
          java-version: 11
      - name: Build
        run: ./bot.sh --prepare
      - uses: actions/setup-node@v1
        name: Install semantic-release
        if: github.ref == 'refs/heads/master' && github.event_name == 'push'
          node-version: '12.x'
      - name: Release
        if: github.ref == 'refs/heads/master' && github.event_name == 'push'
        run: |
          npm install -g semantic-release @semantic-release/{git,changelog,exec}
          GITHUB_TOKEN: ${{ github.token }}

We are done ! Thanks for reading. Github