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.