Kaniko for Container Builds in Java Applications

Comprehensive Kaniko Implementation Guide

1. Kaniko Build Configuration

Maven Plugin Configuration

<!-- pom.xml -->
<project>
<properties>
<docker.image.name>my-java-app</docker.image.name>
<docker.registry>ghcr.io</docker.registry>
<docker.tag>${project.version}</docker.tag>
<jib-maven-plugin.version>3.3.2</jib-maven-plugin.version>
</properties>
<build>
<plugins>
<!-- Kaniko build preparation plugin -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>${jib-maven-plugin.version}</version>
<configuration>
<from>
<image>eclipse-temurin:11-jre</image>
</from>
<to>
<image>${docker.registry}/${docker.image.name}:${docker.tag}</image>
</to>
<container>
<ports>
<port>8080</port>
</ports>
<environment>
<JAVA_OPTS>-XX:+UseContainerSupport</JAVA_OPTS>
</environment>
<labels>
<version>${project.version}</version>
<built-by>kaniko</built-by>
</labels>
</container>
</configuration>
</plugin>
<!-- Build helper for Docker context -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>prepare-docker-context</id>
<phase>package</phase>
<configuration>
<target>
<copy file="${project.build.directory}/${project.build.finalName}.jar" 
tofile="docker-context/app.jar"/>
<copy file="Dockerfile" todir="docker-context"/>
<copy file="entrypoint.sh" todir="docker-context"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

2. Dockerfile Optimized for Kaniko

# Multi-stage build optimized for Kaniko caching
FROM maven:3.8.6-eclipse-temurin-11 AS builder
# Create layer for dependencies
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
# Create layer for source code and build
COPY src ./src
RUN mvn clean package -DskipTests
# Runtime stage
FROM eclipse-temurin:11-jre-jammy
# Install security updates and required packages
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Create application directory
WORKDIR /app
# Copy built artifact from builder stage
COPY --from=builder /app/target/*.jar app.jar
COPY --chown=appuser:appuser entrypoint.sh .
# Set permissions
RUN chmod +x entrypoint.sh && \
chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# JVM options for container
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError"
# Expose port
EXPOSE 8080
# Use shell form for entrypoint to allow variable expansion
ENTRYPOINT ["/app/entrypoint.sh"]

3. Entrypoint Script

#!/bin/bash
# entrypoint.sh
set -e
# Wait for dependencies (if any)
if [ -n "$WAIT_FOR_HOSTS" ]; then
for host in $(echo $WAIT_FOR_HOSTS | sed "s/,/ /g"); do
echo "Waiting for $host..."
while ! nc -z $(echo $host | sed "s/:/ /"); do
sleep 1
done
echo "$host is available"
done
fi
# Set default JVM options if not provided
if [ -z "$JAVA_OPTS" ]; then
JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
fi
# Start the application
exec java $JAVA_OPTS -jar app.jar "$@"

4. Kubernetes Kaniko Build Configuration

Kaniko Pod Template for Kubernetes

# kaniko-pod-template.yaml
apiVersion: v1
kind: Pod
metadata:
name: kaniko-build-pod
annotations:
sidecar.istio.io/inject: "false"
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:v1.9.1
args:
- --dockerfile=Dockerfile
- --context=dir:///workspace
- --destination=$(DOCKER_REGISTRY)/$(APP_NAME):$(VERSION)
- --cache=true
- --cache-ttl=24h
- --cache-repo=$(DOCKER_REGISTRY)/cache/$(APP_NAME)
- --build-arg=BUILD_DATE=$(BUILD_DATE)
- --build-arg=GIT_COMMIT=$(GIT_COMMIT)
- --verbosity=info
- --snapshotMode=redo
- --use-new-run
env:
- name: DOCKER_CONFIG
value: /kaniko/.docker
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
volumeMounts:
- name: kaniko-secret
mountPath: /kaniko/.docker
- name: workspace
mountPath: /workspace
- name: maven-cache
mountPath: /root/.m2
volumes:
- name: kaniko-secret
secret:
secretName: docker-registry-secret
items:
- key: .dockerconfigjson
path: config.json
- name: workspace
persistentVolumeClaim:
claimName: build-workspace-pvc
- name: maven-cache
persistentVolumeClaim:
claimName: maven-cache-pvc
restartPolicy: Never

5. Jenkins Pipeline with Kaniko

// Jenkinsfile-kaniko
pipeline {
agent {
kubernetes {
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: jnlp
image: jenkins/inbound-agent:4.11.2-4
args: ['\$(JENKINS_SECRET)', '\$(JENKINS_NAME)']
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
- name: maven
image: maven:3.8.6-eclipse-temurin-11
command: ['cat']
tty: true
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
volumeMounts:
- name: maven-cache
mountPath: /root/.m2
- name: kaniko
image: gcr.io/kaniko-project/executor:v1.9.1
command: ['cat']
tty: true
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
volumeMounts:
- name: kaniko-secret
mountPath: /kaniko/.docker
volumes:
- name: maven-cache
persistentVolumeClaim:
claimName: maven-cache-pvc
- name: kaniko-secret
secret:
secretName: docker-registry-secret
items:
- key: .dockerconfigjson
path: config.json
"""
}
}
environment {
DOCKER_REGISTRY = 'ghcr.io'
APP_NAME = 'my-java-app'
VERSION = "${env.BUILD_NUMBER}-${env.GIT_COMMIT.substring(0, 7)}"
MAVEN_OPTS = '-Dmaven.repo.local=/root/.m2/repository'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build and Test') {
steps {
container('maven') {
sh '''
mvn clean compile
mvn test
mvn jacoco:report
'''
}
}
post {
always {
junit 'target/surefire-reports/*.xml'
jacoco exclusionPattern: '**/test-classes/**'
}
}
}
stage('Security Scan') {
steps {
container('maven') {
sh 'mvn org.owasp:dependency-check-maven:check'
}
}
}
stage('Prepare Docker Context') {
steps {
container('maven') {
sh '''
mvn package -DskipTests
mkdir -p docker-context
cp target/*.jar docker-context/app.jar
cp Dockerfile docker-context/
cp entrypoint.sh docker-context/
chmod +x docker-context/entrypoint.sh
'''
}
}
}
stage('Build Container with Kaniko') {
steps {
container('kaniko') {
sh '''
/kaniko/executor \
--context=/home/jenkins/agent/workspace/${JOB_NAME}/docker-context \
--destination=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} \
--destination=${DOCKER_REGISTRY}/${APP_NAME}:latest \
--cache=true \
--cache-ttl=72h \
--cache-repo=${DOCKER_REGISTRY}/cache/${APP_NAME} \
--build-arg=BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--build-arg=GIT_COMMIT=${GIT_COMMIT} \
--verbosity=info
'''
}
}
}
stage('Scan Container') {
steps {
container('maven') {
sh '''
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest \
image --exit-code 1 \
${DOCKER_REGISTRY}/${APP_NAME}:${VERSION}
'''
}
}
}
stage('Deploy to Staging') {
when {
branch 'main'
}
steps {
container('maven') {
sh '''
kubectl set image deployment/${APP_NAME} \
${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} \
-n staging
'''
}
}
}
}
post {
always {
container('maven') {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
success {
emailext (
subject: "SUCCESS: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
body: "Build successful: ${env.BUILD_URL}",
to: "${env.CHANGE_AUTHOR_EMAIL}"
)
}
failure {
emailext (
subject: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
body: "Build failed: ${env.BUILD_URL}",
to: "${env.CHANGE_AUTHOR_EMAIL}"
)
}
}
}

6. Kubernetes Resources for Kaniko

Persistent Volume Claims

# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: maven-cache-pvc
namespace: build
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: fast-ssd
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: build-workspace-pvc
namespace: build
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: standard

Docker Registry Secret

# docker-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: docker-registry-secret
namespace: build
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: <base64-encoded-docker-config>

7. Java Application with Build Info

Build Info Actuator Endpoint

@Component
public class BuildInfoContributor implements InfoContributor {
private final BuildProperties buildProperties;
public BuildInfoContributor(BuildProperties buildProperties) {
this.buildProperties = buildProperties;
}
@Override
public void contribute(Info.Builder builder) {
Map<String, Object> buildInfo = new HashMap<>();
buildInfo.put("version", buildProperties.getVersion());
buildInfo.put("time", buildProperties.getTime());
buildInfo.put("builder", "kaniko");
buildInfo.put("java", System.getProperty("java.version"));
builder.withDetail("build", buildInfo);
// Add git info if available
try {
ClassPathResource gitProperties = new ClassPathResource("git.properties");
if (gitProperties.exists()) {
Properties props = new Properties();
props.load(gitProperties.getInputStream());
builder.withDetail("git", props);
}
} catch (IOException e) {
// Git properties not available
}
}
}

Health Check with Build Info

@RestController
@RequestMapping("/actuator")
public class BuildInfoController {
@GetMapping("/build-info")
public ResponseEntity<Map<String, Object>> getBuildInfo() {
Map<String, Object> buildInfo = new HashMap<>();
buildInfo.put("version", getClass().getPackage().getImplementationVersion());
buildInfo.put("buildTime", getClass().getPackage().getImplementationVendor());
buildInfo.put("builder", "kaniko");
buildInfo.put("jvm", System.getProperty("java.version"));
return ResponseEntity.ok(buildInfo);
}
}

8. Maven Build Properties

Git Information Plugin

<!-- pom.xml snippet -->
<build>
<plugins>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>4.9.10</version>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<injectAllReactorProjects>true</injectAllReactorProjects>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

9. Kaniko Build Script

#!/bin/bash
# build-with-kaniko.sh
set -e
# Configuration
APP_NAME="my-java-app"
DOCKER_REGISTRY="ghcr.io"
VERSION="${1:-latest}"
CACHE_TTL="72h"
# Build arguments
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
GIT_COMMIT=$(git rev-parse --short HEAD)
echo "Building ${APP_NAME}:${VERSION} with Kaniko"
/kaniko/executor \
--context="${WORKSPACE}" \
--dockerfile="${WORKSPACE}/Dockerfile" \
--destination="${DOCKER_REGISTRY}/${APP_NAME}:${VERSION}" \
--destination="${DOCKER_REGISTRY}/${APP_NAME}:latest" \
--cache=true \
--cache-ttl="${CACHE_TTL}" \
--cache-repo="${DOCKER_REGISTRY}/cache/${APP_NAME}" \
--build-arg="BUILD_DATE=${BUILD_DATE}" \
--build-arg="GIT_COMMIT=${GIT_COMMIT}" \
--build-arg="VERSION=${VERSION}" \
--label="org.opencontainers.image.created=${BUILD_DATE}" \
--label="org.opencontainers.image.revision=${GIT_COMMIT}" \
--label="org.opencontainers.image.version=${VERSION}" \
--verbosity="info" \
--snapshotMode="redo" \
--use-new-run
echo "Build completed: ${DOCKER_REGISTRY}/${APP_NAME}:${VERSION}"

10. Kubernetes Job for Kaniko Build

# kaniko-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: kaniko-build-${BUILD_NUMBER}
namespace: build
spec:
backoffLimit: 1
template:
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:v1.9.1
args:
- --dockerfile=Dockerfile
- --context=git://${GIT_REPO}#${GIT_BRANCH}
- --destination=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION}
- --destination=${DOCKER_REGISTRY}/${APP_NAME}:latest
- --cache=true
- --cache-ttl=72h
- --cache-repo=${DOCKER_REGISTRY}/cache/${APP_NAME}
- --build-arg=BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- --build-arg=GIT_COMMIT=${GIT_COMMIT}
- --build-arg=VERSION=${VERSION}
- --verbosity=info
- --snapshotMode=redo
env:
- name: DOCKER_CONFIG
value: /kaniko/.docker
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
volumeMounts:
- name: kaniko-secret
mountPath: /kaniko/.docker
volumes:
- name: kaniko-secret
secret:
secretName: docker-registry-secret
items:
- key: .dockerconfigjson
path: config.json
restartPolicy: Never

This comprehensive Kaniko setup provides:

  • Dockerless container builds within Kubernetes
  • Efficient caching for faster builds
  • Security scanning integration
  • Multi-stage builds optimized for Java
  • GitOps-friendly configuration
  • Persistent caching for Maven dependencies
  • Build information tracking
  • Kubernetes-native execution

The setup ensures secure, efficient, and reproducible container builds for Java applications without requiring Docker daemon access.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper