Skip to content

Instantly share code, notes, and snippets.

@melcloud
Created June 5, 2025 12:25
Show Gist options
  • Select an option

  • Save melcloud/491ccc88dd3f26ed5587bc5bb7d23af5 to your computer and use it in GitHub Desktop.

Select an option

Save melcloud/491ccc88dd3f26ed5587bc5bb7d23af5 to your computer and use it in GitHub Desktop.
# 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