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.