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