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.