Codefresh CI/CD for Java: Complete Implementation Guide

Introduction

Codefresh is a modern CI/CD platform built for containers and microservices. It provides native Docker support, powerful workflows, and excellent integration with Kubernetes. This guide covers setting up a comprehensive CI/CD pipeline for Java applications using Codefresh.


Architecture Overview

[Git Repository] → [Codefresh Pipeline] → [Build & Test] → [Container Registry] → [Deployment]
↓                  ↓                    ↓                 ↓                 ↓
GitHub         Pipeline Definition    Unit Tests       Docker Hub        Kubernetes
GitLab         Docker Environment     Integration      ECR               Helm Charts
Bitbucket      Parallel Steps         Security Scan    GCR               Argo CD

Step 1: Project Structure & Configuration

Sample Project Structure

java-microservice/
├── .codefresh/
│   └── codefresh.yml
├── src/
│   └── main/
│       └── java/
│           └── com/example/
├── Dockerfile
├── Dockerfile.dev
├── helm/
│   └── myapp/
│       ├── Chart.yaml
│       ├── values.yaml
│       └── templates/
├── k8s/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── ingress.yaml
├── pom.xml
├── .dockerignore
├── .gitignore
└── README.md

Maven Configuration

pom.xml with CI/CD plugins

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>java-microservice</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Java Microservice</name>
<description>Spring Boot microservice with Codefresh CI/CD</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Test properties -->
<junit.version>5.10.0</junit.version>
<mockito.version>5.5.0</mockito.version>
<!-- Code quality -->
<jacoco.version>0.8.10</jacoco.version>
<sonar.version>3.9.1.2184</sonar.version>
<spotbugs.version>4.7.3.6</spotbugs.version>
<!-- Docker -->
<jib.version>3.4.0</jib.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.18.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.18.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Spring Boot Maven Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!-- JaCoCo for code coverage -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- SpotBugs for static analysis -->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${spotbugs.version}</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<failOnError>true</failOnError>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- JIB for Docker builds -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>${jib.version}</version>
<configuration>
<from>
<image>eclipse-temurin:17-jre-jammy</image>
</from>
<to>
<image>myregistry/java-microservice</image>
</to>
<container>
<ports>
<port>8080</port>
</ports>
<environment>
<JAVA_OPTS>-XX:+UseContainerSupport -Djava.security.egd=file:/dev/./urandom</JAVA_OPTS>
</environment>
</container>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>ci</id>
<properties>
<skip.integration.tests>false</skip.integration.tests>
</properties>
</profile>
</profiles>
</project>

Step 2: Docker Configuration

Production Dockerfile

Dockerfile

# Build stage
FROM eclipse-temurin:17-jdk-jammy as builder
WORKDIR /app
# Copy Maven wrapper and pom.xml
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
# Copy source code
COPY src src
# Build the application
RUN ./mvnw clean package -DskipTests
# Runtime stage
FROM eclipse-temurin:17-jre-jammy
# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Create app user
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
# Copy JAR from build stage
COPY --from=builder /app/target/*.jar app.jar
# Create necessary directories and set permissions
RUN mkdir -p /app/logs && chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# JVM options
ENV JAVA_OPTS="-XX:+UseContainerSupport -Djava.security.egd=file:/dev/./urandom -Dspring.profiles.active=prod"
# Run the application
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Development Dockerfile

Dockerfile.dev

FROM eclipse-temurin:17-jdk-jammy
# Install additional tools for development
RUN apt-get update && apt-get install -y \
curl \
vim \
git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy Maven wrapper
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
# Download dependencies
RUN ./mvnw dependency:go-offline
# Copy source code
COPY src src
# Expose port for debugging
EXPOSE 8080 5005
# Run with debug enabled and hot reload
CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"]

Docker Ignore File

.dockerignore

.git
.gitignore
README.md
**/target/
**/.project
**/.classpath
**/.settings/
**/.factorypath
**/.mvn/
mvnw
mvnw.cmd
**/node_modules/
**/dist/
**/build/
**/.gradle/
**/logs/
**/.idea/
**/*.iml
**/Dockerfile*
**/docker-compose*
**/helm/charts/
**/test-reports/
**/coverage/

Step 3: Codefresh Pipeline Configuration

Main Pipeline Configuration

.codefresh/codefresh.yml

version: '1.0'
# Global variables
variables:
- IMAGE_NAME: 'mycompany/java-microservice'
- HELM_RELEASE_NAME: 'java-microservice'
- KUBE_NAMESPACE: 'production'
- SLACK_CHANNEL: '#deployments'
# Pipeline steps
steps:
# Clone repository
clone:
title: "Clone Repository"
type: "git-clone"
repo: "${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}"
revision: "${{CF_BRANCH}}"
git: "github"
stage: "prepare"
# Build Docker image
build:
title: "Build Docker Image"
type: "build"
image_name: "${{IMAGE_NAME}}"
working_directory: "${{clone}}"
tag: "${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
dockerfile: "Dockerfile"
registry: "dockerhub"
stage: "build"
# Build arguments
build_arguments:
- BUILD_VERSION=${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}
- BUILD_DATE=${{CF_BUILD_TIMESTAMP}}
# Run unit tests
unit_tests:
title: "Run Unit Tests"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn clean test jacoco:report -Pci
stage: "test"
# Test results reporting
when:
condition:
all:
run_unit_tests: 'true'
# Run integration tests
integration_tests:
title: "Run Integration Tests"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn verify -Pci -Dskip.integration.tests=false
stage: "test"
# Environment variables for tests
environment:
- TESTCONTAINERS_RYUK_DISABLED=true
- SPRING_PROFILES_ACTIVE=test
# Security scan
security_scan:
title: "Security Scan"
image: aquasec/trivy:0.45
working_directory: "${{clone}}"
commands:
- trivy filesystem --exit-code 1 --severity HIGH,CRITICAL .
stage: "security"
when:
condition:
all:
run_security_scan: 'true'
# Static code analysis
sonarqube_scan:
title: "SonarQube Analysis"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn sonar:sonar -Dsonar.projectKey=java-microservice -Dsonar.host.url=${{SONARQUBE_URL}} -Dsonar.login=${{SONARQUBE_TOKEN}}
stage: "quality"
environment:
- SONARQUBE_URL=${{SONARQUBE_URL}}
- SONARQUBE_TOKEN=${{SONARQUBE_TOKEN}}
# Push to registry
push_to_registry:
title: "Push to Docker Registry"
type: "push"
candidate: "${{build}}"
tag: "${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
registry: "dockerhub"
stage: "publish"
when:
condition:
any:
- main: 'true'
- develop: 'true'
- '"${{CF_BRANCH}}" =~ /^release\\/.*/'
# Deploy to development
deploy_dev:
title: "Deploy to Development"
type: "kubernetes"
kind: "apply"
cluster: "my-dev-cluster"
namespace: "development"
manifest: "k8s/deployment.yaml"
arguments:
image: "${{build}}"
value: "image.tag=${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
stage: "deploy"
when:
condition:
any:
- main: 'true'
- develop: 'true'
- '"${{CF_BRANCH}}" =~ /^feature\\/.*/'
# Run smoke tests
smoke_tests:
title: "Smoke Tests"
image: curlimages/curl:8.2.1
working_directory: "${{clone}}"
commands:
- |
echo "Running smoke tests..."
# Wait for deployment to be ready
sleep 30
# Test health endpoint
curl -f http://java-microservice.development.svc.cluster.local:8080/actuator/health
echo "Health check passed"
# Test main endpoint
curl -f http://java-microservice.development.svc.cluster.local:8080/api/v1/status
echo "API endpoint check passed"
stage: "verify"
when:
condition:
any:
- main: 'true'
- develop: 'true'
- '"${{CF_BRANCH}}" =~ /^feature\\/.*/'
# Deploy to staging
deploy_staging:
title: "Deploy to Staging"
type: "helm"
cluster: "my-staging-cluster"
namespace: "staging"
chart_name: "./helm/myapp"
release_name: "${{HELM_RELEASE_NAME}}"
values:
- "image.repository=${{IMAGE_NAME}}"
- "image.tag=${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
- "environment=staging"
stage: "deploy"
when:
condition:
any:
- main: 'true'
- '"${{CF_BRANCH}}" =~ /^release\\/.*/'
# Run integration tests in staging
staging_tests:
title: "Staging Integration Tests"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- |
mvn test -Pintegration-test -Dtest.environment=staging -Dservice.url=http://${{HELM_RELEASE_NAME}}.staging.svc.cluster.local:8080
stage: "verify"
when:
condition:
any:
- main: 'true'
- '"${{CF_BRANCH}}" =~ /^release\\/.*/'
# Deploy to production
deploy_production:
title: "Deploy to Production"
type: "helm"
cluster: "my-production-cluster"
namespace: "${{KUBE_NAMESPACE}}"
chart_name: "./helm/myapp"
release_name: "${{HELM_RELEASE_NAME}}"
values:
- "image.repository=${{IMAGE_NAME}}"
- "image.tag=${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
- "environment=production"
- "replicaCount=3"
stage: "deploy"
when:
condition:
all:
- main: 'true'
- approved: 'true'
# Notify Slack
notify_slack:
title: "Notify Slack"
image: codefresh/cli
working_directory: "${{clone}}"
commands:
- |
if [ "${{CF_BUILD_STATUS}}" = "success" ]; then
cf_export SLACK_MESSAGE="✅ Deployment successful: ${{CF_REPO_OWNER}}/${{CF_REPO_NAME}} (${{CF_BRANCH}}) by ${{CF_COMMIT_AUTHOR}}"
cf_export SLACK_COLOR="good"
else
cf_export SLACK_MESSAGE="❌ Deployment failed: ${{CF_REPO_OWNER}}/${{CF_REPO_NAME}} (${{CF_BRANCH}}) by ${{CF_COMMIT_AUTHOR}}"
cf_export SLACK_COLOR="danger"
fi
environment:
- SLACK_WEBHOOK_URL=${{SLACK_WEBHOOK_URL}}
stage: "notify"
# Pipeline triggers
triggers:
- type: "git"
provider: "github"
repo: "${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}"
events:
- push
- pull_request
branches:
- main
- develop
- feature/*
- release/*
- hotfix/*
# Approval gates
approval:
deploy_production:
type: "cf-approval"
title: "Approve Production Deployment"
description: "Please review and approve deployment to production"
users:
- production-approvers
tags:
- production
- deployment

Advanced Pipeline with Matrix Builds

.codefresh/advanced-pipeline.yml

version: '1.0'
steps:
# Parallel testing across multiple Java versions
test_matrix:
title: "Test Matrix"
stage: "test"
type: "parallel"
steps:
test_java_17:
title: "Test Java 17"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn clean test
test_java_21:
title: "Test Java 21"
image: maven:3.9-eclipse-temurin-21
working_directory: "${{clone}}"
commands:
- mvn clean test
# Security scanning in parallel
security_checks:
title: "Security Checks"
stage: "security"
type: "parallel"
steps:
trivy_scan:
title: "Vulnerability Scan"
image: aquasec/trivy:0.45
working_directory: "${{clone}}"
commands:
- trivy filesystem --exit-code 1 --severity HIGH,CRITICAL .
hadolint_scan:
title: "Dockerfile Lint"
image: hadolint/hadolint:2.12.0
working_directory: "${{clone}}"
commands:
- hadolint Dockerfile
spotbugs_scan:
title: "Static Analysis"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn spotbugs:check
# Multi-arch build
multi_arch_build:
title: "Multi-arch Build"
stage: "build"
type: "build"
arguments:
platform: linux/amd64,linux/arm64
image_name: "${{IMAGE_NAME}}"
working_directory: "${{clone}}"
tag: "${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
dockerfile: "Dockerfile"
registry: "dockerhub"

Step 4: Kubernetes Manifests

Basic Deployment

k8s/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: java-microservice
namespace: development
labels:
app: java-microservice
version: "{{image.tag}}"
spec:
replicas: 2
selector:
matchLabels:
app: java-microservice
template:
metadata:
labels:
app: java-microservice
version: "{{image.tag}}"
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
spec:
containers:
- name: java-microservice
image: "{{image.repository}}:{{image.tag}}"
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "dev"
- name: JAVA_OPTS
value: "-XX:+UseContainerSupport -Djava.security.egd=file:/dev/./urandom -Xmx512m -Xms256m"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 15
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
---
apiVersion: v1
kind: Service
metadata:
name: java-microservice
namespace: development
spec:
selector:
app: java-microservice
ports:
- port: 8080
targetPort: 8080
type: ClusterIP

Helm Chart

helm/myapp/Chart.yaml

apiVersion: v2
name: java-microservice
description: A Spring Boot Java microservice
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
- name: postgresql
version: 12.1.0
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled

helm/myapp/values.yaml

# Default values for java-microservice
replicaCount: 2
image:
repository: mycompany/java-microservice
tag: latest
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
podAnnotations: {}
podLabels: {}
podSecurityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
service:
type: ClusterIP
port: 8080
targetPort: 8080
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
env:
- name: SPRING_PROFILES_ACTIVE
value: "production"
- name: JAVA_OPTS
value: "-XX:+UseContainerSupport -Djava.security.egd=file:/dev/./urandom -Xmx512m -Xms256m"
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 15
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
postgresql:
enabled: true
auth:
postgresPassword: "changeme"
database: "mydatabase"
username: "myuser"
password: "mypassword"

helm/myapp/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "java-microservice.fullname" . }}
labels:
{{- include "java-microservice.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "java-microservice.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "java-microservice.selectorLabels" . | nindent 8 }}
version: {{ .Values.image.tag | quote }}
annotations:
{{- with .Values.podAnnotations }}
{{ toYaml . | nindent 8 }}
{{- end }}
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "java-microservice.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
startupProbe:
{{- toYaml .Values.startupProbe | nindent 12 }}
volumeMounts:
- name: logs
mountPath: /app/logs
volumes:
- name: logs
emptyDir: {}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

Step 5: Testing Configuration

Test Configuration

src/test/resources/application-test.yml

spring:
datasource:
url: jdbc:tc:postgresql:15:///testdb
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
sql:
init:
mode: always
logging:
level:
com.example: DEBUG
org.springframework.test: INFO
management:
endpoints:
web:
exposure:
include: health,info,metrics

Integration Test Base Class

BaseIntegrationTest.java

package com.example;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Testcontainers;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@Testcontainers
public abstract class BaseIntegrationTest {
static final PostgreSQLContainer<?> postgres = 
new PostgreSQLContainer<>("postgres:15-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
static {
postgres.start();
}
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}

Pipeline Test Script

.codefresh/test-pipeline.yml

version: '1.0'
steps:
# Cache Maven dependencies
cache_dependencies:
title: "Cache Maven Dependencies"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn dependency:go-offline
stage: "prepare"
# Unit tests with coverage
unit_tests_with_coverage:
title: "Unit Tests with Coverage"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn clean test jacoco:report
- mvn jacoco:check
stage: "test"
# Store test results
on_success:
metadata:
set:
- ${{clone}}/target/surefire-reports/*.xml:type=text/xml
- ${{clone}}/target/site/jacoco/:type=folder
# Integration tests
integration_tests:
title: "Integration Tests"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn verify -Dskip.integration.tests=false
stage: "test"
environment:
- TESTCONTAINERS_RYUK_DISABLED=true
# Performance tests
performance_tests:
title: "Performance Tests"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn gatling:test -Pperformance
stage: "test"
when:
condition:
all:
run_performance_tests: 'true'
# Test report generation
test_reports:
title: "Generate Test Reports"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn surefire-report:report-only
- echo "## Test Results" >> ${{CF_VOLUME_PATH}}/reports.md
- echo "- Unit Tests: $(find target/surefire-reports -name '*.xml' | wc -l) test cases" >> ${{CF_VOLUME_PATH}}/reports.md
- echo "- Coverage: $(cat target/site/jacoco/index.html | grep -o 'Total[^%]*%' | head -1)" >> ${{CF_VOLUME_PATH}}/reports.md
stage: "report"

Step 6: Security & Compliance

Security Scanning Pipeline

.codefresh/security-pipeline.yml

version: '1.0'
steps:
# SAST with SonarQube
sonarqube_analysis:
title: "SonarQube Analysis"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn sonar:sonar 
-Dsonar.projectKey=java-microservice 
-Dsonar.host.url=${{SONARQUBE_URL}} 
-Dsonar.login=${{SONARQUBE_TOKEN}}
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
stage: "security"
# Dependency vulnerability scanning
dependency_check:
title: "Dependency Vulnerability Scan"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn org.owasp:dependency-check-maven:check -DfailBuildOnCVSS=7
stage: "security"
# Container vulnerability scanning
container_scan:
title: "Container Vulnerability Scan"
image: aquasec/trivy:0.45
working_directory: "${{clone}}"
commands:
- trivy image --exit-code 1 --severity HIGH,CRITICAL ${{IMAGE_NAME}}:${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}
stage: "security"
# Secrets detection
secrets_scan:
title: "Secrets Detection"
image: gitguardian/ggshield:latest
working_directory: "${{clone}}"
commands:
- ggshield scan path . --exit-zero
stage: "security"
# License compliance
license_check:
title: "License Compliance"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn org.codehaus.mojo:license-maven-plugin:download-licenses
- mvn org.codehaus.mojo:license-maven-plugin:check-licenses
stage: "compliance"

Step 7: Environment-Specific Pipelines

Development Pipeline

.codefresh/development-pipeline.yml

version: '1.0'
steps:
# Fast development build
fast_build:
title: "Fast Development Build"
type: "build"
image_name: "${{IMAGE_NAME}}"
working_directory: "${{clone}}"
tag: "dev-${{CF_SHORT_REVISION}}"
dockerfile: "Dockerfile.dev"
build_arguments:
- DEV_MODE=true
stage: "build"
# Quick tests
quick_tests:
title: "Quick Tests"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn clean test -Dtest=!*IntegrationTest
stage: "test"
# Deploy to development namespace
deploy_dev:
title: "Deploy to Development"
type: "kubernetes"
kind: "apply"
cluster: "dev-cluster"
namespace: "development"
manifest: "k8s/dev-deployment.yaml"
arguments:
image: "${{fast_build}}"
stage: "deploy"

Production Pipeline

.codefresh/production-pipeline.yml

version: '1.0'
steps:
# Security gates
security_approval:
title: "Security Approval"
type: "cf-approval"
topic: "Security Review"
description: "Security team must approve before production deployment"
users:
- security-team
stage: "approval"
# Compliance check
compliance_check:
title: "Compliance Check"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- mvn license:check
- mvn owasp:dependency-check -DfailBuildOnCVSS=4
stage: "compliance"
# Production deployment with canary
canary_deploy:
title: "Canary Deployment"
type: "helm"
cluster: "production-cluster"
namespace: "production"
chart_name: "./helm/myapp"
release_name: "${{HELM_RELEASE_NAME}}"
values:
- "image.repository=${{IMAGE_NAME}}"
- "image.tag=${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
- "canary.enabled=true"
- "canary.weight=10"
stage: "deploy"
# Canary validation
canary_validation:
title: "Canary Validation"
image: curlimages/curl:8.2.1
working_directory: "${{clone}}"
commands:
- |
# Monitor canary metrics for 5 minutes
for i in {1..30}; do
response=$(curl -s -o /dev/null -w "%{http_code}" http://canary.java-microservice.production.svc.cluster.local:8080/actuator/health)
if [ "$response" != "200" ]; then
echo "Canary health check failed"
exit 1
fi
sleep 10
done
echo "Canary validation successful"
stage: "verify"
# Full rollout
full_deploy:
title: "Full Production Rollout"
type: "helm"
cluster: "production-cluster"
namespace: "production"
chart_name: "./helm/myapp"
release_name: "${{HELM_RELEASE_NAME}}"
values:
- "image.repository=${{IMAGE_NAME}}"
- "image.tag=${{CF_BRANCH_TAG_NORMALIZED}}-${{CF_SHORT_REVISION}}"
- "canary.enabled=false"
- "replicaCount=5"
stage: "deploy"

Step 8: Monitoring & Observability

Pipeline Metrics

.codefresh/metrics-pipeline.yml

version: '1.0'
steps:
# Collect build metrics
build_metrics:
title: "Collect Build Metrics"
image: codefresh/cli
working_directory: "${{clone}}"
commands:
- |
# Push metrics to Prometheus
BUILD_DURATION=$(( $(date +%s) - $(date -d "${{CF_BUILD_INITIATED}}" +%s) ))
echo "build_duration_seconds $BUILD_DURATION" >> metrics.txt
echo "build_success 1" >> metrics.txt
echo "test_coverage $(cat target/site/jacoco/jacoco.xml | grep -o 'line-coverage[^%]*%' | cut -d'\"' -f2)" >> metrics.txt
stage: "metrics"
# Quality gates
quality_gate:
title: "Quality Gate"
image: maven:3.9-eclipse-temurin-17
working_directory: "${{clone}}"
commands:
- |
COVERAGE=$(mvn jacoco:check | grep -o 'Coverage checks.*' | cut -d' ' -f3)
if (( $(echo "$COVERAGE < 0.8" | bc -l) )); then
echo "Quality gate failed: Coverage $COVERAGE < 80%"
exit 1
fi
echo "Quality gate passed"
stage: "quality"

Best Practices

1. Pipeline Optimization

  • Use parallel steps for independent tasks
  • Cache dependencies between builds
  • Use smaller base images for faster builds
  • Implement incremental testing where possible

2. Security

  • Scan for vulnerabilities in dependencies and containers
  • Use minimal base images
  • Implement secrets management
  • Regular security compliance checks

3. Reliability

  • Implement proper health checks
  • Use canary deployments for production
  • Comprehensive testing at each stage
  • Rollback strategies

4. Monitoring

  • Track pipeline performance metrics
  • Monitor application performance post-deployment
  • Implement alerting for pipeline failures
  • Log aggregation and analysis

5. Cost Optimization

  • Use spot instances for non-production environments
  • Implement resource limits
  • Clean up old images and resources
  • Monitor and optimize build times

Conclusion

This comprehensive Codefresh CI/CD setup for Java provides:

  • Fast, reliable builds with Docker and Maven optimization
  • Comprehensive testing with unit, integration, and security tests
  • Multi-environment deployment with proper promotion gates
  • Security scanning at every stage of the pipeline
  • Kubernetes-native deployment with Helm charts
  • Advanced deployment strategies like canary releases
  • Monitoring and observability integration

By implementing this pipeline, you achieve:

  • Faster time to market with automated deployments
  • Higher quality with comprehensive testing and security scanning
  • Better reliability with proper health checks and rollback strategies
  • Improved collaboration with clear pipeline stages and approvals
  • Cost efficiency with optimized resource usage and caching

This setup provides a solid foundation for modern Java application delivery that can scale with your organization's needs.

Leave a Reply

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


Macro Nepal Helper