Created
June 5, 2025 12:25
-
-
Save melcloud/491ccc88dd3f26ed5587bc5bb7d23af5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Multi-stage Dockerfile for building many Java projects with Maven | |
| # Dynamically discovers all POMs and caches dependencies efficiently | |
| # Maven cache preparation stage - discovers and caches ALL dependencies | |
| FROM maven:3.9-eclipse-temurin-17 AS maven-cache-prep | |
| WORKDIR /workspace | |
| # Install additional tools | |
| RUN apt-get update && apt-get install -y git curl rsync findutils && rm -rf /var/lib/apt/lists/* | |
| # Copy entire source tree to discover all POMs | |
| COPY . . | |
| # Copy Maven settings | |
| COPY settings.xml /root/.m2/settings.xml 2>/dev/null || echo "No settings.xml found, using defaults" | |
| # Create script to find and process all POMs, handling root POM specially | |
| RUN cat > /cache-deps.sh << 'EOF' | |
| #!/bin/bash | |
| set -e | |
| echo "π Discovering all POM files..." | |
| # Check for root-level pom.xml first | |
| ROOT_POM="" | |
| if [ -f "./pom.xml" ]; then | |
| ROOT_POM="./pom.xml" | |
| echo "π³ Found root POM: $ROOT_POM" | |
| # Check if root POM has modules (multi-module project) | |
| if grep -q "<modules>" "./pom.xml" 2>/dev/null; then | |
| echo "π¦ Root POM is a multi-module parent - will build from root" | |
| # For multi-module projects, build from root to get everything | |
| echo "π Building entire project tree from root..." | |
| cd "/workspace" | |
| # Download dependencies for entire project tree | |
| if mvn dependency:go-offline -B -q 2>/dev/null; then | |
| echo "β All dependencies cached for multi-module project" | |
| else | |
| echo "β οΈ Warning: Could not cache all dependencies for multi-module project" | |
| fi | |
| # Also resolve plugin dependencies | |
| if mvn dependency:resolve-plugins -B -q 2>/dev/null; then | |
| echo "β All plugin dependencies cached for multi-module project" | |
| else | |
| echo "β οΈ Warning: Could not cache all plugin dependencies" | |
| fi | |
| # Create manifest of the root build | |
| echo "." > /root/.m2/cached-projects.txt | |
| echo "π Multi-module project dependencies cached from root!" | |
| exit 0 | |
| else | |
| echo "π Root POM is standalone (no modules)" | |
| fi | |
| fi | |
| # Find all other pom.xml files, excluding target directories and hidden directories | |
| POM_FILES=$(find . -name "pom.xml" -not -path "*/target/*" -not -path "*/.*" | sort) | |
| echo "Found POM files:" | |
| echo "$POM_FILES" | |
| # If we have a root POM but it's not multi-module, still process it first | |
| if [ -n "$ROOT_POM" ]; then | |
| echo "π¦ Processing root POM first: $ROOT_POM" | |
| cd "/workspace" | |
| if mvn dependency:go-offline -B -q 2>/dev/null; then | |
| echo "β Root dependencies cached" | |
| else | |
| echo "β οΈ Warning: Could not cache root dependencies" | |
| fi | |
| if mvn dependency:resolve-plugins -B -q 2>/dev/null; then | |
| echo "β Root plugin dependencies cached" | |
| else | |
| echo "β οΈ Warning: Could not cache root plugin dependencies" | |
| fi | |
| fi | |
| # Process each remaining POM directory (skip root if already processed) | |
| for pom in $POM_FILES; do | |
| pom_dir=$(dirname "$pom") | |
| # Skip root if already processed | |
| if [ "$pom" = "$ROOT_POM" ]; then | |
| continue | |
| fi | |
| echo "π¦ Processing dependencies for: $pom_dir" | |
| cd "/workspace/$pom_dir" | |
| # Download dependencies with error handling | |
| if mvn dependency:go-offline -B -q 2>/dev/null; then | |
| echo "β Dependencies cached for: $pom_dir" | |
| else | |
| echo "β οΈ Warning: Could not cache dependencies for: $pom_dir (might inherit from parent)" | |
| fi | |
| # Also resolve plugin dependencies | |
| if mvn dependency:resolve-plugins -B -q 2>/dev/null; then | |
| echo "β Plugin dependencies cached for: $pom_dir" | |
| else | |
| echo "β οΈ Warning: Could not cache plugin dependencies for: $pom_dir" | |
| fi | |
| cd "/workspace" | |
| done | |
| echo "π All discoverable dependencies cached!" | |
| # Show cache size | |
| echo "π Maven cache size:" | |
| du -sh /root/.m2/repository 2>/dev/null || echo "Cache directory not found" | |
| # Create a manifest of cached projects | |
| echo "π Creating project manifest..." | |
| echo "$POM_FILES" > /root/.m2/cached-projects.txt | |
| EOF | |
| RUN chmod +x /cache-deps.sh | |
| # Execute the caching script with shared cache mount | |
| RUN --mount=type=cache,target=/root/.m2/repository,id=maven-deps,sharing=shared \ | |
| --mount=type=cache,target=/root/.m2/wrapper,id=maven-wrapper,sharing=shared \ | |
| /cache-deps.sh | |
| # Export Maven cache stage - creates a layer with ALL Maven dependencies | |
| FROM scratch AS maven-cache-export | |
| COPY --from=maven-cache-prep /root/.m2/repository /maven-cache/repository | |
| COPY --from=maven-cache-prep /root/.m2/wrapper /maven-cache/wrapper | |
| COPY --from=maven-cache-prep /root/.m2/cached-projects.txt /maven-cache/cached-projects.txt | |
| # Project discovery stage - finds all projects and creates build matrix | |
| FROM maven:3.9-eclipse-temurin-17 AS project-discovery | |
| WORKDIR /workspace | |
| # Copy source tree | |
| COPY . . | |
| # Create script to discover and categorize projects, handling root POM | |
| RUN cat > /discover-projects.sh << 'EOF' | |
| #!/bin/bash | |
| set -e | |
| echo "π Discovering and categorizing projects..." | |
| # Check for root-level pom.xml first | |
| ROOT_POM="" | |
| IS_MULTI_MODULE=false | |
| if [ -f "./pom.xml" ]; then | |
| ROOT_POM="./pom.xml" | |
| echo "π³ Found root POM: $ROOT_POM" | |
| # Check if root POM has modules (multi-module project) | |
| if grep -q "<modules>" "./pom.xml" 2>/dev/null; then | |
| IS_MULTI_MODULE=true | |
| echo "π¦ Root POM is a multi-module parent" | |
| # Extract module names from root POM | |
| DECLARED_MODULES=$(grep -A 20 "<modules>" "./pom.xml" | grep "<module>" | sed 's/.*<module>\(.*\)<\/module>.*/\1/' | tr '\n' ' ') | |
| echo "π Declared modules: $DECLARED_MODULES" | |
| else | |
| echo "π Root POM is standalone (no modules)" | |
| fi | |
| fi | |
| # Find all pom.xml files | |
| POM_FILES=$(find . -name "pom.xml" -not -path "*/target/*" -not -path "*/.*" | sort) | |
| # Categorize POMs | |
| ROOT_BUILD="" | |
| PARENT_POMS="" | |
| MODULE_POMS="" | |
| STANDALONE_POMS="" | |
| # If we have a multi-module root, treat it specially | |
| if [ "$IS_MULTI_MODULE" = true ]; then | |
| ROOT_BUILD="." | |
| echo "π³ Multi-module root build: ." | |
| # All other POMs in a multi-module project are likely modules | |
| for pom in $POM_FILES; do | |
| pom_dir=$(dirname "$pom") | |
| # Skip the root POM itself | |
| if [ "$pom" = "./pom.xml" ]; then | |
| continue | |
| fi | |
| # Check if it's a declared module | |
| module_name=$(basename "$pom_dir") | |
| if echo "$DECLARED_MODULES" | grep -q "\b$module_name\b"; then | |
| MODULE_POMS="$MODULE_POMS $pom_dir" | |
| echo "π§© Declared module: $pom_dir" | |
| else | |
| # Might be a nested module or standalone | |
| if grep -q "<parent>" "$pom" 2>/dev/null; then | |
| MODULE_POMS="$MODULE_POMS $pom_dir" | |
| echo "π§© Nested module: $pom_dir" | |
| else | |
| STANDALONE_POMS="$STANDALONE_POMS $pom_dir" | |
| echo "π Standalone in multi-module: $pom_dir" | |
| fi | |
| fi | |
| done | |
| else | |
| # No multi-module root, categorize normally | |
| for pom in $POM_FILES; do | |
| pom_dir=$(dirname "$pom") | |
| # Check if it's a parent POM (has modules) | |
| if grep -q "<modules>" "$pom" 2>/dev/null; then | |
| PARENT_POMS="$PARENT_POMS $pom_dir" | |
| echo "π¨βπ©βπ§βπ¦ Parent POM: $pom_dir" | |
| # Check if it's part of a multi-module project (has parent) | |
| elif grep -q "<parent>" "$pom" 2>/dev/null && [ "$pom_dir" != "." ]; then | |
| MODULE_POMS="$MODULE_POMS $pom_dir" | |
| echo "π§© Module POM: $pom_dir" | |
| else | |
| STANDALONE_POMS="$STANDALONE_POMS $pom_dir" | |
| echo "π Standalone POM: $pom_dir" | |
| fi | |
| done | |
| fi | |
| # Save discovery results | |
| mkdir -p /tmp/project-info | |
| echo "$ROOT_BUILD" > /tmp/project-info/root-build.txt | |
| echo "$PARENT_POMS" > /tmp/project-info/parent-poms.txt | |
| echo "$MODULE_POMS" > /tmp/project-info/module-poms.txt | |
| echo "$STANDALONE_POMS" > /tmp/project-info/standalone-poms.txt | |
| echo "$IS_MULTI_MODULE" > /tmp/project-info/is-multi-module.txt | |
| echo "π Project Summary:" | |
| echo "Root multi-module build: $(echo $ROOT_BUILD | wc -w)" | |
| echo "Parent POMs: $(echo $PARENT_POMS | wc -w)" | |
| echo "Module POMs: $(echo $MODULE_POMS | wc -w)" | |
| echo "Standalone POMs: $(echo $STANDALONE_POMS | wc -w)" | |
| echo "Is multi-module project: $IS_MULTI_MODULE" | |
| EOF | |
| RUN chmod +x /discover-projects.sh && /discover-projects.sh | |
| # Base build stage with cached dependencies | |
| FROM maven:3.9-eclipse-temurin-17 AS maven-base | |
| WORKDIR /workspace | |
| # Install tools | |
| RUN apt-get update && apt-get install -y git curl parallel && rm -rf /var/lib/apt/lists/* | |
| # Copy Maven settings | |
| COPY settings.xml /root/.m2/settings.xml 2>/dev/null || echo "No settings.xml found" | |
| # Initialize cache (dependencies should already be available from cache mount) | |
| RUN --mount=type=cache,target=/root/.m2/repository,id=maven-deps,sharing=shared \ | |
| --mount=type=cache,target=/root/.m2/wrapper,id=maven-wrapper,sharing=shared \ | |
| echo "Maven cache initialized for builds" | |
| # Dynamic build stage - builds all discovered projects | |
| FROM maven-base AS build-all-projects | |
| WORKDIR /workspace | |
| # Copy entire source tree | |
| COPY . . | |
| # Copy project discovery results | |
| COPY --from=project-discovery /tmp/project-info /tmp/project-info | |
| # Create build script that handles root POM intelligently | |
| RUN cat > /build-all.sh << 'EOF' | |
| #!/bin/bash | |
| set -e | |
| echo "ποΈ Building all discovered projects..." | |
| # Read project discovery results | |
| IS_MULTI_MODULE=$(cat /tmp/project-info/is-multi-module.txt) | |
| ROOT_BUILD=$(cat /tmp/project-info/root-build.txt | tr -d '\n') | |
| # Function to build a single project | |
| build_project() { | |
| local project_dir="$1" | |
| local project_type="$2" | |
| echo "π¨ Building $project_type: $project_dir" | |
| cd "/workspace/$project_dir" | |
| # Skip parent-only POMs that don't produce artifacts | |
| if [ "$project_type" = "parent" ] && ! ls src 2>/dev/null; then | |
| echo "βοΈ Skipping parent-only POM: $project_dir" | |
| return 0 | |
| fi | |
| # Build with appropriate strategy | |
| if mvn clean package -B -DskipTests=false -q; then | |
| echo "β Successfully built: $project_dir" | |
| # Find and list produced artifacts | |
| if [ -d "target" ]; then | |
| find target -name "*.jar" -o -name "*.war" -o -name "*.ear" | head -5 | while read artifact; do | |
| echo " π¦ Artifact: $artifact" | |
| done | |
| fi | |
| else | |
| echo "β Failed to build: $project_dir" | |
| return 1 | |
| fi | |
| } | |
| export -f build_project | |
| # If this is a multi-module project, build from root | |
| if [ "$IS_MULTI_MODULE" = "true" ] && [ -n "$ROOT_BUILD" ]; then | |
| echo "π³ Building multi-module project from root..." | |
| cd "/workspace" | |
| echo "π¨ Building entire project tree from root..." | |
| if mvn clean package -B -DskipTests=false -q; then | |
| echo "β Successfully built multi-module project from root" | |
| # Find all produced artifacts | |
| echo "π¦ All artifacts produced:" | |
| find . -name "*.jar" -o -name "*.war" -o -name "*.ear" | grep target | head -20 | while read artifact; do | |
| echo " π¦ $artifact" | |
| done | |
| else | |
| echo "β Failed to build multi-module project from root" | |
| exit 1 | |
| fi | |
| else | |
| echo "π Building individual projects..." | |
| # Build parent POMs first (if any) | |
| if [ -s /tmp/project-info/parent-poms.txt ]; then | |
| echo "π¨βπ©βπ§βπ¦ Building parent POMs first..." | |
| for parent in $(cat /tmp/project-info/parent-poms.txt); do | |
| build_project "$parent" "parent" | |
| done | |
| fi | |
| # Build standalone projects in parallel | |
| if [ -s /tmp/project-info/standalone-poms.txt ]; then | |
| echo "π Building standalone projects in parallel..." | |
| cat /tmp/project-info/standalone-poms.txt | tr ' ' '\n' | grep -v '^$' | \ | |
| parallel -j$(nproc) build_project {} "standalone" | |
| fi | |
| # Build module projects in parallel (they should have their parent already built) | |
| if [ -s /tmp/project-info/module-poms.txt ]; then | |
| echo "π§© Building module projects in parallel..." | |
| cat /tmp/project-info/module-poms.txt | tr ' ' '\n' | grep -v '^$' | \ | |
| parallel -j$(nproc) build_project {} "module" | |
| fi | |
| fi | |
| echo "π All projects built successfully!" | |
| # Create build summary | |
| echo "π Build Summary:" | |
| find /workspace -name "*.jar" -o -name "*.war" -o -name "*.ear" | grep target | wc -l | xargs echo "Total artifacts produced:" | |
| EOF | |
| RUN chmod +x /build-all.sh | |
| # Execute build with cache mounts | |
| RUN --mount=type=cache,target=/root/.m2/repository,id=maven-deps,sharing=shared \ | |
| --mount=type=cache,target=/root/.m2/wrapper,id=maven-wrapper,sharing=shared \ | |
| /build-all.sh | |
| # Test stage - runs tests for all projects | |
| FROM maven-base AS test-all-projects | |
| WORKDIR /workspace | |
| # Copy entire source tree and project info | |
| COPY . . | |
| COPY --from=project-discovery /tmp/project-info /tmp/project-info | |
| # Create test script that handles root POM appropriately | |
| RUN cat > /test-all.sh << 'EOF' | |
| #!/bin/bash | |
| set -e | |
| echo "π§ͺ Running tests for all projects..." | |
| # Read project discovery results | |
| IS_MULTI_MODULE=$(cat /tmp/project-info/is-multi-module.txt) | |
| ROOT_BUILD=$(cat /tmp/project-info/root-build.txt | tr -d '\n') | |
| # Function to test a single project | |
| test_project() { | |
| local project_dir="$1" | |
| echo "π§ͺ Testing: $project_dir" | |
| cd "/workspace/$project_dir" | |
| # Skip if no test directory | |
| if [ ! -d "src/test" ] && [ ! -d "*/src/test" ]; then | |
| echo "βοΈ No tests found in: $project_dir" | |
| return 0 | |
| fi | |
| if mvn test -B -q; then | |
| echo "β Tests passed: $project_dir" | |
| else | |
| echo "β Tests failed: $project_dir" | |
| return 1 | |
| fi | |
| } | |
| export -f test_project | |
| # If this is a multi-module project, test from root | |
| if [ "$IS_MULTI_MODULE" = "true" ] && [ -n "$ROOT_BUILD" ]; then | |
| echo "π³ Running tests for multi-module project from root..." | |
| cd "/workspace" | |
| if mvn test -B -q; then | |
| echo "β All tests passed for multi-module project" | |
| else | |
| echo "β Some tests failed in multi-module project" | |
| exit 1 | |
| fi | |
| else | |
| echo "π Running tests for individual projects..." | |
| # Run tests in parallel for all projects with tests | |
| ALL_PROJECTS="" | |
| for file in /tmp/project-info/*.txt; do | |
| if [ -s "$file" ] && [ "$(basename $file)" != "is-multi-module.txt" ] && [ "$(basename $file)" != "root-build.txt" ]; then | |
| ALL_PROJECTS="$ALL_PROJECTS $(cat $file)" | |
| fi | |
| done | |
| echo "$ALL_PROJECTS" | tr ' ' '\n' | grep -v '^$' | sort -u | \ | |
| parallel -j$(nproc) test_project | |
| fi | |
| echo "π All tests completed!" | |
| EOF | |
| RUN chmod +x /test-all.sh | |
| # Execute tests with cache mounts | |
| RUN --mount=type=cache,target=/root/.m2/repository,id=maven-deps,sharing=shared \ | |
| --mount=type=cache,target=/root/.m2/wrapper,id=maven-wrapper,sharing=shared \ | |
| /test-all.sh | |
| # Final runtime stage - collects all artifacts | |
| FROM eclipse-temurin:17-jre-alpine AS runtime | |
| # Create non-root user | |
| RUN addgroup -g 1000 appgroup && \ | |
| adduser -u 1000 -G appgroup -s /bin/sh -D appuser | |
| # Create application directory structure | |
| RUN mkdir -p /app/artifacts /app/scripts && \ | |
| chown -R appuser:appgroup /app | |
| # Copy all built artifacts from the build stage | |
| COPY --from=build-all-projects --chown=appuser:appgroup /workspace/*/target/*.jar /app/artifacts/ 2>/dev/null || echo "No JAR files found" | |
| COPY --from=build-all-projects --chown=appuser:appgroup /workspace/*/target/*.war /app/artifacts/ 2>/dev/null || echo "No WAR files found" | |
| COPY --from=build-all-projects --chown=appuser:appgroup /workspace/*/target/*.ear /app/artifacts/ 2>/dev/null || echo "No EAR files found" | |
| # Copy project information | |
| COPY --from=project-discovery --chown=appuser:appgroup /tmp/project-info /app/project-info | |
| # Create startup script | |
| RUN cat > /app/scripts/startup.sh << 'EOF' | |
| #!/bin/sh | |
| echo "π Starting multi-project application..." | |
| echo "π¦ Available artifacts:" | |
| ls -la /app/artifacts/ | |
| echo "π Project information:" | |
| cat /app/project-info/*.txt | |
| echo "Ready to run applications!" | |
| exec "$@" | |
| EOF | |
| RUN chmod +x /app/scripts/startup.sh && \ | |
| chown appuser:appgroup /app/scripts/startup.sh | |
| # Switch to non-root user | |
| USER appuser | |
| WORKDIR /app | |
| # Health check | |
| HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ | |
| CMD echo "Health check passed" || exit 1 | |
| # Expose common ports | |
| EXPOSE 8080 8081 8082 8083 8084 8085 | |
| # Default command | |
| CMD ["/app/scripts/startup.sh"] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment