Article
Codefresh is a modern CI/CD platform built specifically for cloud-native applications, offering robust container-based pipelines and excellent Kubernetes support. For Java teams, Codefresh provides powerful capabilities to automate builds, tests, security scans, and deployments across multiple environments.
In this guide, we'll explore how to configure comprehensive CI/CD pipelines for Java applications using Codefresh, covering everything from basic Maven builds to advanced Kubernetes deployments.
Why Codefresh for Java CI/CD?
- Container-First Approach: Native Docker and Kubernetes support
- Rich Pipeline Visualizations: Real-time pipeline monitoring and debugging
- Hybrid Runner Support: Run pipelines on Codefresh infrastructure or your own runners
- Integrated Registry: Built-in Docker registry with vulnerability scanning
- Helm and Kustomize Support: Native Kubernetes deployment tools
- Security Scanning: Built-in SCA and SAST security checks
Part 1: Project Setup and Configuration
1.1 Basic Project Structure
java-application/ ├── .codefresh/ │ └── codefresh.yml ├── src/ │ └── main/java/com/example/ ├── Dockerfile ├── Dockerfile.dev ├── helm/ │ └── myapp/ ├── pom.xml ├── .dockerignore └── .gitignore
1.2 Codefresh Account Setup
- Create Codefresh Account: Sign up at codefresh.io
- Connect Source Control: Link your GitHub, GitLab, or Bitbucket repository
- Configure Container Registry: Connect Docker Hub, ECR, GCR, or Codefresh registry
- Set Up Kubernetes Cluster: Connect your target Kubernetes clusters
Part 2: Pipeline Configuration
2.1 Basic Maven Pipeline
# .codefresh/codefresh.yml
version: '1.0'
# Global environment variables
env:
IMAGE_NAME: 'my-java-app'
MAVEN_IMAGE: 'maven:3.8.6-openjdk-17'
GRADLE_IMAGE: 'gradle:7.4.2-jdk17'
stages:
- prepare
- test
- build
- security_scan
- deploy
steps:
clone:
title: 'Clone Repository'
type: 'git-clone'
repo: '${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}'
revision: '${{CF_REVISION}}'
stage: prepare
unit_tests:
title: 'Run Unit Tests'
stage: test
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- mvn clean test
- mvn jacoco:report
environment:
- MAVEN_OPTS=-Dmaven.repo.local=/codefresh/volume/m2_repository
integration_tests:
title: 'Run Integration Tests'
stage: test
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- mvn verify -DskipUnitTests
when:
condition:
all:
branchMaster: '"${{CF_BRANCH}}" == "master"'
notPR: '${{CF_PULL_REQUEST}}' == ''
build_jar:
title: 'Build Application JAR'
stage: build
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- mvn clean package -DskipTests
- ls -la target/
environment:
- MAVEN_OPTS=-Dmaven.repo.local=/codefresh/volume/m2_repository
build_image:
title: 'Build Docker Image'
stage: build
type: 'build'
image_name: '${{IMAGE_NAME}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
tag: '${{CF_SHORT_REVISION}}'
dockerfile: 'Dockerfile'
registry: 'dockerhub'
when:
condition:
all:
branchMaster: '"${{CF_BRANCH}}" == "master"'
notPR: '${{CF_PULL_REQUEST}}' == ''
push_image:
title: 'Push Docker Image'
stage: build
type: 'push'
candidate: '${{build_image}}'
tag: '${{CF_BRANCH}}-${{CF_SHORT_REVISION}}'
registry: 'dockerhub'
when:
condition:
all:
branchMaster: '"${{CF_BRANCH}}" == "master"'
notPR: '${{CF_PULL_REQUEST}}' == ''
2.2 Multi-Stage Dockerfile
# Dockerfile # Build stage FROM maven:3.8.6-openjdk-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # Runtime stage FROM eclipse-temurin:17-jre-alpine WORKDIR /app # Install required packages RUN apk add --no-cache curl jq # Copy application JAR COPY --from=builder /app/target/*.jar app.jar # Create non-root user RUN addgroup -S spring && adduser -S spring -G spring USER spring # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"]
2.3 Development Dockerfile
# Dockerfile.dev FROM maven:3.8.6-openjdk-17 WORKDIR /app # Copy Maven configuration COPY pom.xml . RUN mvn dependency:go-offline # Copy source code COPY src ./src # Enable debug and dev tools ENV JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" EXPOSE 5005 8080 CMD ["mvn", "spring-boot:run"]
Part 3: Advanced Pipeline Features
3.1 Multi-Branch Pipeline with Conditions
# .codefresh/advanced-pipeline.yml
version: '1.0'
stages:
- prepare
- test
- build
- security
- deploy_dev
- deploy_staging
- deploy_prod
steps:
clone:
title: 'Clone Repository'
type: 'git-clone'
repo: '${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}'
revision: '${{CF_REVISION}}'
stage: prepare
# Conditional steps based on branch
unit_tests:
title: 'Run Unit Tests'
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- mvn clean test
- mvn jacoco:report
stage: test
# Only run integration tests on main branches
integration_tests:
title: 'Run Integration Tests'
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- mvn verify -DskipUnitTests
stage: test
when:
condition:
any:
- master: '"${{CF_BRANCH}}" == "master"'
- main: '"${{CF_BRANCH}}" == "main"'
- develop: '"${{CF_BRANCH}}" == "develop"'
# Build and test for feature branches
build_feature_branch:
title: 'Build Feature Branch'
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- mvn clean package
- mvn sonar:sonar -Dsonar.projectKey=my-java-app -Dsonar.branch.name=${{CF_BRANCH}}
stage: build
when:
condition:
all:
isFeature: '"${{CF_BRANCH}}" =~ ^feature/'
notPR: '${{CF_PULL_REQUEST}}' == ''
# Security scanning
security_scan:
title: 'Security Scan'
stage: security
image: 'aquasec/trivy:latest'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- trivy filesystem --exit-code 1 --no-progress .
when:
condition:
any:
- master: '"${{CF_BRANCH}}" == "master"'
- main: '"${{CF_BRANCH}}" == "main"'
# Dependency vulnerability check
dependency_check:
title: 'Dependency Vulnerability Scan'
stage: security
image: 'owasp/dependency-check:7.1.1'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- dependency-check.sh --project "my-java-app" --scan . --out . --format HTML
- cp dependency-check-report.html /codefresh/volume/
when:
condition:
any:
- master: '"${{CF_BRANCH}}" == "master"'
- main: '"${{CF_BRANCH}}" == "main"'
3.2 Parallel Test Execution
parallel_tests:
title: 'Parallel Test Execution'
stage: test
type: 'parallel'
steps:
unit_tests_core:
title: 'Core Unit Tests'
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- mvn test -Dtest=com.example.core.*Test
unit_tests_service:
title: 'Service Layer Tests'
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- mvn test -Dtest=com.example.service.*Test
unit_tests_controller:
title: 'Controller Tests'
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- mvn test -Dtest=com.example.controller.*Test
when:
condition:
any:
- master: '"${{CF_BRANCH}}" == "master"'
- main: '"${{CF_BRANCH}}" == "main"'
Part 4: Kubernetes Deployment
4.1 Helm-Based Deployment
# Kubernetes deployment steps
deploy_development:
title: 'Deploy to Development'
stage: deploy_dev
image: 'alpine/helm:3.10.0'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- echo "Deploying to development namespace"
- helm upgrade --install my-app ./helm/myapp \
--namespace development \
--set image.tag=${{CF_SHORT_REVISION}} \
--set environment=dev \
--wait --timeout 5m
when:
condition:
any:
- master: '"${{CF_BRANCH}}" == "master"'
- main: '"${{CF_BRANCH}}" == "main"'
deploy_staging:
title: 'Deploy to Staging'
stage: deploy_staging
image: 'alpine/helm:3.10.0'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- echo "Deploying to staging namespace"
- helm upgrade --install my-app ./helm/myapp \
--namespace staging \
--set image.tag=${{CF_SHORT_REVISION}} \
--set environment=staging \
--wait --timeout 5m
when:
condition:
branchMaster: '"${{CF_BRANCH}}" == "master"'
# Manual approval for production
production_approval:
title: 'Approve Production Deployment'
stage: deploy_prod
type: 'approval'
when:
condition:
branchMaster: '"${{CF_BRANCH}}" == "master"'
deploy_production:
title: 'Deploy to Production'
stage: deploy_prod
image: 'alpine/helm:3.10.0'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- echo "Deploying to production namespace"
- helm upgrade --install my-app ./helm/myapp \
--namespace production \
--set image.tag=${{CF_SHORT_REVISION}} \
--set environment=prod \
--set replicaCount=3 \
--wait --timeout 10m
when:
condition:
branchMaster: '"${{CF_BRANCH}}" == "master"'
4.2 Sample Helm Chart
# helm/myapp/values.yaml replicaCount: 2 image: repository: my-registry/my-java-app pullPolicy: IfNotPresent tag: "latest" service: type: ClusterIP port: 8080 ingress: enabled: true className: "nginx" hosts: - host: myapp.example.com paths: - path: / pathType: Prefix resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1024Mi" cpu: "500m" autoscaling: enabled: true minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 80 env: SPRING_PROFILES_ACTIVE: "prod" JAVA_OPTS: "-Xmx512m -Xms256m"
Part 5: Database Migrations and Services
5.1 Database Migration Step
database_migrations:
title: 'Run Database Migrations'
stage: deploy_dev
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- |
echo "Running database migrations"
mvn liquibase:update \
-Dliquibase.url=${{DB_URL}} \
-Dliquibase.username=${{DB_USERNAME}} \
-Dliquibase.password=${{DB_PASSWORD}}
environment:
- DB_URL=${{DEV_DB_URL}}
- DB_USERNAME=${{DEV_DB_USERNAME}}
- DB_PASSWORD=${{DEV_DB_PASSWORD}}
when:
condition:
any:
- master: '"${{CF_BRANCH}}" == "master"'
- main: '"${{CF_BRANCH}}" == "main"'
5.2 Service Container for Integration Testing
integration_test_with_services:
title: 'Integration Tests with Databases'
stage: test
services:
composition:
postgres:
image: postgres:14
environment:
- POSTGRES_DB=testdb
- POSTGRES_USER=testuser
- POSTGRES_PASSWORD=testpass
ports:
- 5432
redis:
image: redis:7-alpine
ports:
- 6379
steps:
run_integration_tests:
image: '${{MAVEN_IMAGE}}'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- |
# Wait for services to be ready
./wait-for-it.sh postgres:5432 --timeout=30
./wait-for-it.sh redis:6379 --timeout=30
# Run integration tests
mvn verify -DskipUnitTests \
-Dspring.datasource.url=jdbc:postgresql://postgres:5432/testdb \
-Dspring.datasource.username=testuser \
-Dspring.datasource.password=testpass \
-Dspring.redis.host=redis
Part 6: Notifications and Monitoring
6.1 Pipeline Notifications
notifications: - type: 'slack' channel: 'ci-cd-alerts' when: pipeline: - failed - finished - type: 'email' users: - [email protected] when: pipeline: - failed - type: 'webhook' url: '${{ALERT_WEBHOOK_URL}}' when: pipeline: - failed # Success notification for important branches success_notification: title: 'Notify Success' image: 'alpine/curl:latest' commands: - | curl -X POST -H 'Content-type: application/json' \ --data '{"text":"Pipeline completed successfully for ${{CF_BRANCH}} branch"}' \ ${{SLACK_WEBHOOK_URL}} when: condition: all: pipelineSuccess: '${{CF_PIPELINE_TRIGGER_ID}}' != '' importantBranch: any: - master: '"${{CF_BRANCH}}" == "master"' - main: '"${{CF_BRANCH}}" == "main"'
6.2 Post-Deployment Verification
smoke_tests:
title: 'Post-Deployment Smoke Tests'
stage: deploy_dev
image: 'curlimages/curl:latest'
commands:
- |
echo "Running smoke tests..."
# Test health endpoint
curl -f http://my-app-development:8080/actuator/health || exit 1
# Test main endpoint
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://my-app-development:8080/api/v1/status)
if [ "$RESPONSE" -ne 200 ]; then
echo "Smoke test failed: HTTP $RESPONSE"
exit 1
fi
echo "Smoke tests passed!"
when:
condition:
any:
- master: '"${{CF_BRANCH}}" == "master"'
- main: '"${{CF_BRANCH}}" == "main"'
Part 7: Environment-Specific Configuration
7.1 Codefresh Variables
Configure these in Codefresh UI → Pipeline Settings → Variables:
# Environment-specific variables
DEV_DB_URL: 'jdbc:postgresql://dev-db.example.com:5432/myapp'
STAGING_DB_URL: 'jdbc:postgresql://staging-db.example.com:5432/myapp'
PROD_DB_URL: 'jdbc:postgresql://prod-db.example.com:5432/myapp'
# Registry configuration
REGISTRY_URL: 'myregistry.example.com'
REGISTRY_USERNAME: '${{DOCKERHUB_USERNAME}}'
# Slack webhook for notifications
SLACK_WEBHOOK_URL: '${{SLACK_CI_WEBHOOK}}'
# SonarQube configuration
SONAR_HOST_URL: 'https://sonar.example.com'
SONAR_TOKEN: '${{SONARQUBE_TOKEN}}'
7.2 Conditional Environment Deployment
conditional_deployment:
title: 'Conditional Environment Deployment'
stage: deploy_dev
image: 'alpine/helm:3.10.0'
working_directory: '${{CF_VOLUME_PATH}}/app'
commands:
- |
if [ "${{CF_BRANCH}}" == "develop" ]; then
ENVIRONMENT="dev"
NAMESPACE="development"
elif [ "${{CF_BRANCH}}" == "staging" ]; then
ENVIRONMENT="staging"
NAMESPACE="staging"
elif [ "${{CF_BRANCH}}" == "master" ]; then
ENVIRONMENT="prod"
NAMESPACE="production"
else
echo "No deployment for branch ${{CF_BRANCH}}"
exit 0
fi
helm upgrade --install my-app ./helm/myapp \
--namespace $NAMESPACE \
--set image.tag=${{CF_SHORT_REVISION}} \
--set environment=$ENVIRONMENT \
--wait --timeout 5m
Best Practices for Codefresh Java CI/CD
- Use Caching: Leverage Codefresh cache for Maven/Gradle dependencies
- Security First: Integrate security scanning early in the pipeline
- Parallel Execution: Run independent tests and builds in parallel
- Environment Isolation: Use separate namespaces and configurations for each environment
- Rollback Strategy: Implement automated rollback for failed deployments
- Resource Optimization: Use appropriate resource requests/limits in Kubernetes
- Monitoring Integration: Connect with Prometheus, Grafana, or application monitoring
- Secret Management: Use Codefresh secrets or external secret managers
Conclusion
Codefresh provides a powerful, flexible platform for implementing comprehensive CI/CD pipelines for Java applications. By leveraging its container-native approach, rich visualization, and Kubernetes integration, you can create efficient pipelines that:
- Automate the entire software delivery process from code commit to production deployment
- Ensure code quality with comprehensive testing and security scanning
- Enable safe deployments with gradual rollouts and rollback capabilities
- Provide full visibility into pipeline execution and application health
The examples in this guide provide a solid foundation that you can adapt to your specific Java application requirements, whether you're using Spring Boot, Quarkus, Micronaut, or other Java frameworks.
Pyroscope Profiling in Java
Explains how to use Pyroscope for continuous profiling in Java applications, helping developers analyze CPU and memory usage patterns to improve performance and identify bottlenecks.
https://macronepal.com/blog/pyroscope-profiling-in-java/
OpenTelemetry Metrics in Java: Comprehensive Guide
Provides a complete guide to collecting and exporting metrics in Java using OpenTelemetry, including counters, histograms, gauges, and integration with monitoring tools. (MACRO NEPAL)
https://macronepal.com/blog/opentelemetry-metrics-in-java-comprehensive-guide/
OTLP Exporter in Java: Complete Guide for OpenTelemetry
Explains how to configure OTLP exporters in Java to send telemetry data such as traces, metrics, and logs to monitoring systems using HTTP or gRPC protocols. (MACRO NEPAL)
https://macronepal.com/blog/otlp-exporter-in-java-complete-guide-for-opentelemetry/
Thanos Integration in Java: Global View of Metrics
Explains how to integrate Thanos with Java monitoring systems to create a scalable global metrics view across multiple Prometheus instances.
https://macronepal.com/blog/thanos-integration-in-java-global-view-of-metrics
Time Series with InfluxDB in Java: Complete Guide (Version 2)
Explains how to manage time-series data using InfluxDB in Java applications, including storing, querying, and analyzing metrics data.
https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide-2
Time Series with InfluxDB in Java: Complete Guide
Provides an overview of integrating InfluxDB with Java for time-series data handling, including monitoring applications and managing performance metrics.
https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide
Implementing Prometheus Remote Write in Java (Version 2)
Explains how to configure Java applications to send metrics data to Prometheus-compatible systems using the remote write feature for scalable monitoring.
https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide-2
Implementing Prometheus Remote Write in Java: Complete Guide
Provides instructions for sending metrics from Java services to Prometheus servers, enabling centralized monitoring and real-time analytics.
https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide
Building a TileServer GL in Java: Vector and Raster Tile Server
Explains how to build a TileServer GL in Java for serving vector and raster map tiles, useful for geographic visualization and mapping applications.
https://macronepal.com/blog/building-a-tileserver-gl-in-java-vector-and-raster-tile-server
Indoor Mapping in Java
Explains how to create indoor mapping systems in Java, including navigation inside buildings, spatial data handling, and visualization techniques.