This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 27f0643105 create build script and unified docker file, fixes #6100
(#6103)
27f0643105 is described below
commit 27f064310528c581551f35cf74368363e7c748eb
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Dec 2 16:05:22 2025 +0100
create build script and unified docker file, fixes #6100 (#6103)
---
.dockerignore | 27 +-
Jenkinsfile | 6 +-
docker/build-hop-images.sh | 581 +++++++++++++++++++++
docker/create_hop_rest_container.sh | 2 +-
docker/create_hop_web_container.sh | 2 +-
...ataflowTemplate => dataflowTemplate.Dockerfile} | 0
.../integration-tests/integration-tests-base.yaml | 2 +-
...Dockerfile.unit-tests => unit-tests.Dockerfile} | 0
docker/{Dockerfile.rest => rest.Dockerfile} | 0
docker/unified.Dockerfile | 389 ++++++++++++++
...Dockerfile.web-fatjar => web-fatjar.Dockerfile} | 0
docker/{Dockerfile.web => web.Dockerfile} | 0
docs/hop-dev-manual/modules/ROOT/nav.adoc | 1 +
.../modules/ROOT/pages/docker-build.adoc | 498 ++++++++++++++++++
docs/hop-dev-manual/modules/ROOT/pages/index.adoc | 1 +
.../samples/read-samples-build-hop-run.hpl | 2 +-
16 files changed, 1502 insertions(+), 9 deletions(-)
diff --git a/.dockerignore b/.dockerignore
index b404469ffe..da90c3875b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,7 +1,7 @@
# Ignore everything
*
-# Allow files and directories
+# Allow files and directories needed for OLD Dockerfiles (pre-built artifacts)
!/assemblies/client/target/hop-*
!/assemblies/client/target/hop
!/integration-tests/scripts
@@ -9,4 +9,27 @@
!/assemblies/plugins/target
!/docker
!/rest
-!google-key-apache-hop-it.json
\ No newline at end of file
+!google-key-apache-hop-it.json
+
+# Allow source files needed for NEW unified Dockerfile (maven build in Docker)
+!pom.xml
+!/*/pom.xml
+!/*/*/pom.xml
+!/*/*/*/pom.xml
+!/src
+!/*/src
+!/*/*/src
+!/*/*/*/src
+!/core
+!/engine
+!/engine-beam
+!/plugins
+!/assemblies
+!/ui
+!/rap
+!/rcp
+!/lib
+!/lib-jdbc
+!/.mvn
+!mvnw
+!mvnw.cmd
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index afef930a83..8a2fe67d4d 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -159,9 +159,9 @@ pipeline {
//TODO We may never create final/latest version using
CI/CD as we need to follow manual apache release process with signing
sh "docker buildx create --name hop --use"
//Base docker image
- sh "docker buildx build --platform linux/amd64,linux/arm64
. -f docker/Dockerfile.web -t ${DOCKER_REPO_WEB}:${env.POM_VERSION} -t
${DOCKER_REPO_WEB}:Development --push"
+ sh "docker buildx build --platform linux/amd64,linux/arm64
. -f docker/web.Dockerfile -t ${DOCKER_REPO_WEB}:${env.POM_VERSION} -t
${DOCKER_REPO_WEB}:Development --push"
//Image including fat-jar
- sh "docker buildx build --build-arg
HOP_WEB_VERSION=Development --platform linux/amd64,linux/arm64 . -f
docker/Dockerfile.web-fatjar -t ${DOCKER_REPO_WEB}:${env.POM_VERSION}-beam -t
${DOCKER_REPO_WEB}:Development-beam --push"
+ sh "docker buildx build --build-arg
HOP_WEB_VERSION=Development --platform linux/amd64,linux/arm64 . -f
docker/web-fatjar.Dockerfile -t ${DOCKER_REPO_WEB}:${env.POM_VERSION}-beam -t
${DOCKER_REPO_WEB}:Development-beam --push"
sh "docker buildx rm hop"
}
}
@@ -177,7 +177,7 @@ pipeline {
withDockerRegistry([ credentialsId: "dockerhub-hop", url: ""
]) {
//TODO We may never create final/latest version using
CI/CD as we need to follow manual apache release process with signing
sh "docker buildx create --name hop --use"
- sh "docker buildx build --platform linux/amd64,linux/arm64
. -f docker/Dockerfile.dataflowTemplate -t
${DOCKER_REPO_DATAFLOWTEMPLATE}:${env.POM_VERSION} -t
${DOCKER_REPO_DATAFLOWTEMPLATE}:Development --push"
+ sh "docker buildx build --platform linux/amd64,linux/arm64
. -f docker/dataflowTemplate.Dockerfile -t
${DOCKER_REPO_DATAFLOWTEMPLATE}:${env.POM_VERSION} -t
${DOCKER_REPO_DATAFLOWTEMPLATE}:Development --push"
sh "docker buildx rm hop"
}
}
diff --git a/docker/build-hop-images.sh b/docker/build-hop-images.sh
new file mode 100755
index 0000000000..a46403107a
--- /dev/null
+++ b/docker/build-hop-images.sh
@@ -0,0 +1,581 @@
+#!/bin/bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+################################################################################
+# Apache Hop Unified Docker Build Script
+#
+# This script builds all Apache Hop Docker images using a multi-stage build
+# approach. It supports building from local source or from a GitHub tag.
+#
+# Usage:
+# ./build-hop-images.sh [OPTIONS]
+#
+# Options:
+# -s, --source <local|github> Build from local source or GitHub (default:
local)
+# -t, --tag <tag> Git tag/branch to build from (default: main)
+# -r, --repo <url> GitHub repository URL (default:
https://github.com/apache/hop.git)
+# -i, --images <list> Comma-separated list of images to build
(default: all)
+# Options: see ALL_STAGES variable, or 'all'
+# -v, --version <version> Version string for image tagging
(auto-detected if not provided)
+# -p, --push Push images to registry after build
+# -x, --registry <registry> Docker registry prefix (default: none/local,
e.g., apache, myregistry.io)
+# --platforms <platforms> Build platforms (default: current platform,
e.g., linux/amd64,linux/arm64 for multi)
+# --maven-threads <threads> Maven build threads (default: 1C, e.g., 2C,
4, 8)
+# --progress <mode> Docker build output mode: auto (default),
plain (verbose), tty (compact)
+# --builder <type> Builder type: full (Maven, default) or fast
(pre-built artifacts)
+# --no-cache Build without using cache
+# -h, --help Show this help message
+#
+# Examples:
+# # Build all images from local source
+# ./build-hop-images.sh
+#
+# # Build from GitHub tag 2.9.0
+# ./build-hop-images.sh --source github --tag 2.9.0
+#
+# # Fast build using pre-built artifacts (for local dev)
+# ./build-hop-images.sh --builder fast --images web
+#
+# # Build web image with beam variant (includes fat jar for Dataflow)
+# ./build-hop-images.sh --images web-beam
+#
+# # Build specific variants
+# ./build-hop-images.sh --images client-minimal,web-beam
+#
+# # Build for multiple platforms and push to Docker Hub
+# ./build-hop-images.sh --platforms linux/amd64,linux/arm64 --push
--registry apache
+#
+################################################################################
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Available image stages (add new stages here)
+# Format: "baseImage" or "baseImage-variant"
+ALL_STAGES=("client" "web" "web-beam" "dataflow")
+
+# Detect current platform architecture
+detect_platform() {
+ local arch=$(uname -m)
+ case "$arch" in
+ x86_64|amd64)
+ echo "linux/amd64"
+ ;;
+ aarch64|arm64)
+ echo "linux/arm64"
+ ;;
+ *)
+ echo "linux/amd64" # fallback to amd64
+ ;;
+ esac
+}
+
+# Default values
+SOURCE_TYPE="local"
+GIT_TAG="main"
+GIT_REPO="https://github.com/apache/hop.git"
+IMAGES="all"
+VERSION=""
+PUSH=false
+REGISTRY="" # Empty = local registry (no prefix)
+PLATFORMS="$(detect_platform)" # Default to current platform only
+USE_CACHE="true"
+MAVEN_THREADS="1C" # Maven thread count (1C, 2C, or specific number like 4)
+BUILD_PROGRESS="auto" # Docker build progress output (auto, plain, tty)
+BUILDER_TYPE="full" # Builder type: full (Maven build) or fast (pre-built
artifacts)
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
+
+# Stage names follow pattern: <base>-<variant> or just <base>
+# Examples:
+# client → hop
+# client-minimal → hop
+# web → hop-web
+# web-beam → hop-web
+get_image_name() {
+ local stage_name="$1"
+
+ # Check if stage has a variant suffix
+ case "$stage_name" in
+ client*) echo "hop" ;;
+ web*) echo "hop-web" ;;
+
+ dataflow*) echo "hop-dataflow-template" ;;
+ *) echo "" ;;
+ esac
+}
+
+# Extract variant suffix from stage name
+# Examples:
+# client → ""
+# client-minimal → "minimal"
+# web-beam → "beam"
+get_variant_suffix() {
+ local stage_name="$1"
+
+ # Check if there's a dash in the name
+ if [[ "$stage_name" == *"-"* ]]; then
+ # Extract everything after the first dash
+ echo "${stage_name#*-}"
+ else
+ echo ""
+ fi
+}
+
+# Function to print colored messages
+print_info() {
+ echo -e "${BLUE}ℹ${NC} $1"
+}
+
+print_success() {
+ echo -e "${GREEN}✓${NC} $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}⚠${NC} $1"
+}
+
+print_error() {
+ echo -e "${RED}✗${NC} $1"
+}
+
+print_header() {
+ echo -e
"\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
+ echo -e "${BLUE} $1${NC}"
+ echo -e
"${BLUE}═══════════════════════════════════════════════════════════${NC}\n"
+}
+
+# Function to show help
+show_help() {
+ sed -n '/^#
Usage:/,/^################################################################################$/p'
"$0" | grep -v "^#####" | sed 's/^# //'
+ exit 0
+}
+
+# Function to detect version from pom.xml
+detect_version() {
+ if [ -f "$PROJECT_ROOT/pom.xml" ]; then
+ # Get the project version (not parent version)
+ # Remove parent block, then get first version tag
+ VERSION=$(sed -n '/<parent>/,/<\/parent>/d;
s/.*<version>\(.*\)<\/version>.*/\1/p' "$PROJECT_ROOT/pom.xml" | head -1 |
xargs)
+
+ if [ -z "$VERSION" ]; then
+ # Fallback to maven command if available
+ if command -v mvn &> /dev/null; then
+ VERSION=$(cd "$PROJECT_ROOT" && mvn help:evaluate
-Dexpression=project.version -q -DforceStdout 2>/dev/null || echo "")
+ fi
+ fi
+
+ if [ -z "$VERSION" ]; then
+ VERSION="latest"
+ print_warning "Could not detect version, using: $VERSION"
+ else
+ print_info "Detected version from pom.xml: $VERSION"
+ fi
+ else
+ VERSION="latest"
+ print_warning "Could not detect version, using: $VERSION"
+ fi
+}
+
+# Function to validate prerequisites
+check_prerequisites() {
+ print_header "Checking Prerequisites"
+
+ # Check Docker
+ if ! command -v docker &> /dev/null; then
+ print_error "Docker is not installed or not in PATH"
+ exit 1
+ fi
+ print_success "Docker: $(docker --version)"
+
+ # Check if Docker daemon is running
+ if ! docker info &> /dev/null; then
+ print_error "Docker daemon is not running"
+ exit 1
+ fi
+ print_success "Docker daemon is running"
+
+ # Check buildx for multi-platform builds
+ if echo "$PLATFORMS" | grep -q ","; then
+ if ! docker buildx version &> /dev/null; then
+ print_error "Docker buildx is required for multi-platform builds"
+ exit 1
+ fi
+ print_success "Docker buildx is available"
+ fi
+}
+
+# Function to load environment file if exists
+load_env_file() {
+ local env_file="$SCRIPT_DIR/build.env"
+ if [[ -f "$env_file" ]]; then
+ print_info "Loading configuration from build.env"
+ # shellcheck disable=SC1090
+ source "$env_file"
+ fi
+}
+
+# Function to parse command line arguments
+parse_args() {
+ while [[ $# -gt 0 ]]; do
+ case $1 in
+ -s|--source)
+ SOURCE_TYPE="$2"
+ shift 2
+ ;;
+ -t|--tag)
+ GIT_TAG="$2"
+ shift 2
+ ;;
+ -r|--repo)
+ GIT_REPO="$2"
+ shift 2
+ ;;
+ -i|--images)
+ IMAGES="$2"
+ shift 2
+ ;;
+ -v|--version)
+ VERSION="$2"
+ shift 2
+ ;;
+ -p|--push)
+ PUSH=true
+ shift
+ ;;
+ -x|--registry)
+ REGISTRY="$2"
+ shift 2
+ ;;
+ --platforms)
+ PLATFORMS="$2"
+ shift 2
+ ;;
+ --maven-threads)
+ MAVEN_THREADS="$2"
+ shift 2
+ ;;
+ --progress)
+ BUILD_PROGRESS="$2"
+ shift 2
+ ;;
+ --builder)
+ BUILDER_TYPE="$2"
+ shift 2
+ ;;
+ --no-cache)
+ USE_CACHE="false"
+ shift
+ ;;
+ -h|--help)
+ show_help
+ ;;
+ *)
+ print_error "Unknown option: $1"
+ echo "Use --help for usage information"
+ exit 1
+ ;;
+ esac
+ done
+}
+
+# Function to validate arguments
+validate_args() {
+ # Validate source type
+ if [[ "$SOURCE_TYPE" != "local" && "$SOURCE_TYPE" != "github" ]]; then
+ print_error "Invalid source type: $SOURCE_TYPE (must be 'local' or
'github')"
+ exit 1
+ fi
+
+ # If building from local, check if we're in the right directory
+ if [[ "$SOURCE_TYPE" == "local" && ! -f "$PROJECT_ROOT/pom.xml" ]]; then
+ print_error "pom.xml not found in $PROJECT_ROOT"
+ print_error "Make sure you're running this script from the Hop project
directory"
+ exit 1
+ fi
+}
+
+# Function to build an image
+build_image() {
+ local stage_name=$1
+ local image_name=$(get_image_name "$stage_name")
+ local variant_suffix=$(get_variant_suffix "$stage_name")
+
+ if [ -z "$image_name" ]; then
+ print_warning "Unknown stage name: $stage_name (skipping)"
+ return 1
+ fi
+
+ # Build full image name with optional registry prefix
+ local full_image_name
+ if [ -n "$REGISTRY" ]; then
+ full_image_name="$REGISTRY/$image_name"
+ else
+ full_image_name="$image_name"
+ fi
+
+ # Display what we're building
+ if [ -n "$variant_suffix" ]; then
+ print_header "Building $image_name (variant: $variant_suffix)"
+ else
+ print_header "Building $image_name"
+ fi
+
+ # Detect CPU cores for Maven threading
+ local cpu_cores
+ if [[ "$OSTYPE" == "darwin"* ]]; then
+ cpu_cores=$(sysctl -n hw.ncpu 2>/dev/null || echo "4")
+ else
+ cpu_cores=$(nproc 2>/dev/null || echo "4")
+ fi
+ local maven_threads="${MAVEN_THREADS:-1C}"
+
+ # Determine the primary version tag (include variant suffix if applicable)
+ local version_tag="$VERSION"
+ if [ -n "$variant_suffix" ]; then
+ version_tag="${VERSION}-${variant_suffix}"
+ fi
+
+ # Build arguments
+ local build_args=(
+ "--build-arg" "HOP_BUILD_FROM_SOURCE=$SOURCE_TYPE"
+ "--build-arg" "TARGET_IMAGE=$stage_name"
+ "--build-arg" "MAVEN_THREADS=$maven_threads"
+ "--build-arg" "BUILDER_TYPE=$BUILDER_TYPE"
+ "--progress" "$BUILD_PROGRESS"
+ "--file" "$SCRIPT_DIR/unified.Dockerfile"
+ "--target" "$stage_name"
+ "--tag" "$full_image_name:$version_tag"
+ )
+
+ # Add git arguments if building from GitHub
+ if [[ "$SOURCE_TYPE" == "github" ]]; then
+ build_args+=(
+ "--build-arg" "HOP_GIT_REPO=$GIT_REPO"
+ "--build-arg" "HOP_GIT_TAG=$GIT_TAG"
+ )
+ fi
+
+ # Add cache flag
+ if [[ "$USE_CACHE" == "false" ]]; then
+ build_args+=("--no-cache")
+ fi
+
+ # Add additional tags based on version and variant
+ # Tag format examples:
+ # Standard: hop-web:2.9.0, hop-web:Development, hop-web:latest
+ # Variant: hop-web:2.9.0-beam, hop-web:Development-beam,
hop-web:beam-latest
+ if [[ "$VERSION" =~ SNAPSHOT ]]; then
+ # SNAPSHOT versions get "Development" tag
+ if [ -n "$variant_suffix" ]; then
+ build_args+=("--tag"
"$full_image_name:Development-${variant_suffix}")
+ else
+ build_args+=("--tag" "$full_image_name:Development")
+ fi
+ else
+ # Release versions get "latest" tag
+ if [ -n "$variant_suffix" ]; then
+ build_args+=("--tag" "$full_image_name:${variant_suffix}-latest")
+ else
+ build_args+=("--tag" "$full_image_name:latest")
+ fi
+ fi
+
+ # Build command (primary_tag already set as version_tag above)
+ if echo "$PLATFORMS" | grep -q ","; then
+ # Multi-platform build requires buildx
+ build_args+=(
+ "--platform" "$PLATFORMS"
+ )
+
+ if [[ "$PUSH" == "true" ]]; then
+ build_args+=("--push")
+ else
+ build_args+=("--load")
+ fi
+
+ print_info "Building multi-platform image: $PLATFORMS"
+ # Use default buildx builder (no need to create/destroy)
+ docker buildx build "${build_args[@]}" "$PROJECT_ROOT"
+ else
+ # Single platform build (faster, smaller, can use regular docker build)
+ print_info "Building for platform: $PLATFORMS"
+
+ # Use buildx only if pushing, otherwise use regular docker build
(faster)
+ if [[ "$PUSH" == "true" ]]; then
+ build_args+=(
+ "--platform" "$PLATFORMS"
+ "--push"
+ )
+
+ # Use default buildx builder
+ docker buildx build "${build_args[@]}" "$PROJECT_ROOT"
+ else
+ # Regular docker build for single platform (no buildx overhead)
+ build_args+=(
+ "--platform" "$PLATFORMS"
+ )
+ docker build "${build_args[@]}" "$PROJECT_ROOT"
+ fi
+ fi
+
+ print_success "Successfully built $full_image_name:$version_tag"
+}
+
+# Function to build all requested images
+build_images() {
+ print_header "Build Configuration"
+ echo "Source: $SOURCE_TYPE"
+ if [[ "$SOURCE_TYPE" == "github" ]]; then
+ echo "Repository: $GIT_REPO"
+ echo "Tag: $GIT_TAG"
+ fi
+ echo "Images: $IMAGES"
+ echo "Version: $VERSION"
+ if [ -n "$REGISTRY" ]; then
+ echo "Registry: $REGISTRY"
+ else
+ echo "Registry: local (no prefix)"
+ fi
+ echo "Platforms: $PLATFORMS"
+ echo "Builder type: $BUILDER_TYPE"
+ if [[ "$BUILDER_TYPE" == "full" ]]; then
+ echo "Maven threads: $MAVEN_THREADS"
+ fi
+ echo "Push: $PUSH"
+ echo "Cache: $USE_CACHE"
+ echo ""
+
+ # Determine which images to build
+ local images_to_build=()
+ if [[ "$IMAGES" == "all" ]]; then
+ # Build all images including variants
+ images_to_build=("${ALL_STAGES[@]}")
+ else
+ IFS=',' read -ra images_to_build <<< "$IMAGES"
+ fi
+
+ # Build each image
+ for image in "${images_to_build[@]}"; do
+ image=$(echo "$image" | xargs) # trim whitespace
+ build_image "$image"
+ done
+}
+
+# Function to print summary
+print_summary() {
+ print_header "Build Summary"
+
+ local images_to_check=()
+ if [[ "$IMAGES" == "all" ]]; then
+ # Check all images including variants
+ images_to_check=("${ALL_STAGES[@]}")
+ else
+ IFS=',' read -ra images_to_check <<< "$IMAGES"
+ fi
+
+ echo "Built images:"
+ for image in "${images_to_check[@]}"; do
+ image=$(echo "$image" | xargs)
+ local image_name=$(get_image_name "$image")
+ local variant_suffix=$(get_variant_suffix "$image")
+
+ if [[ -n "$image_name" ]]; then
+ # Build the full image name with registry
+ local full_image_name
+ if [ -n "$REGISTRY" ]; then
+ full_image_name="$REGISTRY/$image_name"
+ else
+ full_image_name="$image_name"
+ fi
+
+ # Primary version tag (with variant suffix if applicable)
+ local version_tag="$VERSION"
+ if [ -n "$variant_suffix" ]; then
+ version_tag="${VERSION}-${variant_suffix}"
+ fi
+ echo " - $full_image_name:$version_tag"
+
+ # Additional alias tag (Development or latest)
+ if [[ "$VERSION" =~ SNAPSHOT ]]; then
+ if [ -n "$variant_suffix" ]; then
+ echo " - $full_image_name:Development-${variant_suffix}"
+ else
+ echo " - $full_image_name:Development"
+ fi
+ else
+ if [ -n "$variant_suffix" ]; then
+ echo " - $full_image_name:${variant_suffix}-latest"
+ else
+ echo " - $full_image_name:latest"
+ fi
+ fi
+ fi
+ done
+
+ if [[ "$PUSH" == "true" ]]; then
+ print_success "Images pushed to registry"
+ else
+ print_info "Images available locally (not pushed)"
+ fi
+
+ echo ""
+ print_success "Build completed successfully!"
+}
+
+################################################################################
+# Main execution
+################################################################################
+
+main() {
+ print_header "Apache Hop Docker Build System"
+
+ # Load environment file if exists (before parsing args so args can
override)
+ load_env_file
+
+ # Parse command line arguments
+ parse_args "$@"
+
+ # Check prerequisites
+ check_prerequisites
+
+ # Validate arguments
+ validate_args
+
+ # Detect version if not provided
+ if [[ -z "$VERSION" ]]; then
+ detect_version
+ fi
+
+ # Build images
+ build_images
+
+ # Print summary
+ print_summary
+}
+
+# Run main function
+main "$@"
+
diff --git a/docker/create_hop_rest_container.sh
b/docker/create_hop_rest_container.sh
index 604f8e6c46..b7e41b394b 100755
--- a/docker/create_hop_rest_container.sh
+++ b/docker/create_hop_rest_container.sh
@@ -43,7 +43,7 @@ cp ../plugins/misc/projects/target/hop-plugins*.jar
../assemblies/plugins/dist/t
# Build the docker image.
#
-docker build ../ -f Dockerfile.rest -t hop-rest # --progress=plain --no-cache
+docker build ../ -f rest.Dockerfile -t hop-rest # --progress=plain --no-cache
# Cleanup
#
diff --git a/docker/create_hop_web_container.sh
b/docker/create_hop_web_container.sh
index b95283ce57..cd31341188 100755
--- a/docker/create_hop_web_container.sh
+++ b/docker/create_hop_web_container.sh
@@ -42,7 +42,7 @@ cp ../rap/target/hop-*SNAPSHOT.jar
../assemblies/web/target/webapp/WEB-INF/lib/
cp ../plugins/engines/beam/target/hop-plugins*.jar
../assemblies/plugins/dist/target/plugins/engines/beam/
#build docker image
-docker build ../ -f Dockerfile.web -t hop-web
+docker build ../ -f web.Dockerfile -t hop-web
#cleanup
rm -rf ../assemblies/web/target/webapp
diff --git a/docker/Dockerfile.dataflowTemplate
b/docker/dataflowTemplate.Dockerfile
similarity index 100%
rename from docker/Dockerfile.dataflowTemplate
rename to docker/dataflowTemplate.Dockerfile
diff --git a/docker/integration-tests/integration-tests-base.yaml
b/docker/integration-tests/integration-tests-base.yaml
index b0483b7aa3..51d1a1028a 100644
--- a/docker/integration-tests/integration-tests-base.yaml
+++ b/docker/integration-tests/integration-tests-base.yaml
@@ -20,7 +20,7 @@ services:
image: hop-base-image
build:
context: ../../.
- dockerfile: docker/integration-tests/Dockerfile.unit-tests
+ dockerfile: docker/integration-tests/unit-tests.Dockerfile
args:
- JENKINS_USER=jenkins
- JENKINS_UID=1000
diff --git a/docker/integration-tests/Dockerfile.unit-tests
b/docker/integration-tests/unit-tests.Dockerfile
similarity index 100%
rename from docker/integration-tests/Dockerfile.unit-tests
rename to docker/integration-tests/unit-tests.Dockerfile
diff --git a/docker/Dockerfile.rest b/docker/rest.Dockerfile
similarity index 100%
rename from docker/Dockerfile.rest
rename to docker/rest.Dockerfile
diff --git a/docker/unified.Dockerfile b/docker/unified.Dockerfile
new file mode 100644
index 0000000000..d828fae106
--- /dev/null
+++ b/docker/unified.Dockerfile
@@ -0,0 +1,389 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+################################################################################
+# Unified Multi-Stage Dockerfile for Apache Hop
+#
+# This Dockerfile can build all Hop container images using multi-stage builds:
+# - hop (client/server)
+# - hop-web
+# - hop-rest
+# - hop-dataflow-template
+#
+# Build arguments:
+# HOP_BUILD_FROM_SOURCE: If "github", clones from GitHub; else uses local
source
+# HOP_GIT_REPO: GitHub repository URL
+# HOP_GIT_TAG: Git tag/branch to build from
+# HOP_VERSION: Version string for labeling
+# TARGET_IMAGE: Which image to build (client, web, rest, dataflow)
+# BUILDER_TYPE: Builder flavor (full, fast)
+################################################################################
+
+# Global build arguments (must be declared before any FROM to use in FROM
statements)
+ARG BUILDER_TYPE=full
+
+################################################################################
+# Stage 1: Source preparation
+################################################################################
+FROM alpine:latest AS source-github
+ARG HOP_GIT_REPO=https://github.com/apache/hop.git
+ARG HOP_GIT_TAG=main
+
+RUN apk add --no-cache git
+WORKDIR /build
+RUN git clone --depth 1 --branch ${HOP_GIT_TAG} ${HOP_GIT_REPO} hop
+
+################################################################################
+# Stage 2a: Full Maven Builder (slower, complete build)
+################################################################################
+FROM maven:3.9-eclipse-temurin-17 AS builder-full
+ARG HOP_BUILD_FROM_SOURCE=local
+ARG MAVEN_OPTS="-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
+ARG MAVEN_THREADS="1C"
+
+# Copy source - either from git clone or local context
+# For GitHub builds, copy from the git clone stage
+COPY --from=source-github /build/hop /tmp/github-hop
+
+# For local builds, copy directly to /build first (more efficient)
+COPY . /build
+
+# If building from GitHub, replace /build with GitHub source
+RUN if [ "${HOP_BUILD_FROM_SOURCE}" = "github" ]; then \
+ rm -rf /build/* /build/.* 2>/dev/null || true; \
+ cp -a /tmp/github-hop/. /build/; \
+ fi
+
+WORKDIR /build
+
+# Verify pom.xml exists
+RUN if [ ! -f "pom.xml" ]; then \
+ echo "ERROR: pom.xml not found in /build"; \
+ echo "Contents of /build:"; \
+ ls -la /build/ || true; \
+ exit 1; \
+ fi
+
+# Build with Maven - produces artifacts in target/ folders
+RUN mvn clean install -T ${MAVEN_THREADS} -B -C -e -V \
+ -Djacoco.skip=true \
+ -Drat.skip=true \
+ -Dcheckstyle.skip=true \
+ -Dspotless.skip=true \
+ -DskipTests=true \
+ -Daether.syncContext.named.time=300 \
+ --file pom.xml
+
+
+
+################################################################################
+# Stage 2b: Fast Builder (uses pre-built artifacts, for local dev)
+################################################################################
+# This builder skips Maven and just copies pre-built artifacts
+# Use this when you've already run "mvn clean install" locally
+# Expects the project to be built with artifacts in target/ folders
+FROM alpine:latest AS builder-fast
+
+WORKDIR /build
+
+# Copy pre-built artifacts from context (must exist!)
+COPY ./assemblies/client/target/hop-client-*.zip
/build/assemblies/client/target/
+COPY ./assemblies/web/target/hop.war /build/assemblies/web/target/
+COPY ./assemblies/plugins/target/hop-assemblies-*.zip
/build/assemblies/plugins/target/
+COPY ./rest/target/hop-rest*.war /build/rest/target/
+COPY ./docker/resources/ /build/docker/resources/
+
+# builder-fast produces the same artifacts as builder-full:
+# - /build/assemblies/client/target/hop-client-*.zip
+# - /build/assemblies/web/target/hop.war
+# - /build/assemblies/plugins/target/hop-assemblies-*.zip
+# - /build/rest/target/hop-rest*.war
+# - /build/docker/resources/*
+#
+# These will be extracted and prepared in Stage 3
+
+################################################################################
+# Stage 2c: Builder Selector
+################################################################################
+# This stage selects which builder to use based on BUILDER_TYPE build arg
+# Default is "full" for complete Maven build
+# NOTE: BUILDER_TYPE is declared globally at the top of this file
+FROM builder-${BUILDER_TYPE} AS builder-selected
+
+################################################################################
+# Stage 3: Common Preparation Stage (ALL preparation logic in ONE place!)
+################################################################################
+# This stage:
+# 1. Extracts/unzips artifacts from Stage 2
+# 2. Generates fat jar
+# 3. Prepares optimized directory structures for final images
+#
+# ANY new builder flavor just needs to produce the same artifacts as Stage 2,
+# and this stage will handle everything else!
+################################################################################
+FROM alpine:latest AS builder
+
+# Install tools needed for preparation
+RUN apk add --no-cache unzip bash openjdk17-jre
+
+WORKDIR /build
+
+# Copy artifacts from selected builder
+COPY --from=builder-selected /build/ /build/
+
+# Step 1: Unzip and extract all assemblies
+RUN echo "=== Extracting assemblies ===" && \
+ # Unzip client assembly
+ if [ -f assemblies/client/target/hop-client-*.zip ]; then \
+ cd assemblies/client/target && \
+ unzip -q hop-client-*.zip && \
+ cd /build; \
+ else \
+ echo "ERROR: Client assembly not found" && exit 1; \
+ fi && \
+ # Unzip web assembly
+ if [ -f assemblies/web/target/hop.war ]; then \
+ cd assemblies/web/target && \
+ unzip -q hop.war -d webapp && \
+ cd /build; \
+ else \
+ echo "WARNING: Web assembly not found, skipping"; \
+ fi && \
+ # Unzip plugins assembly
+ if [ -d assemblies/plugins/target ] && ls
assemblies/plugins/target/hop-assemblies-*.zip 1> /dev/null 2>&1; then \
+ cd assemblies/plugins/target && \
+ unzip -q hop-assemblies-*.zip && \
+ cd /build; \
+ else \
+ echo "WARNING: Plugins assembly not found, will use built plugins
directly"; \
+ fi
+
+# Step 2: Generate fat jar for dataflow template
+RUN if [ -f /build/assemblies/client/target/hop/hop-conf.sh ]; then \
+ /build/assemblies/client/target/hop/hop-conf.sh \
+ --generate-fat-jar=/tmp/hop-fatjar.jar; \
+ else \
+ echo "ERROR: hop-conf.sh not found" && exit 1; \
+ fi
+
+# Step 3: Prepare optimized directory structures for final images
+RUN mkdir -p /build/hop-web-prepared/webapps/ROOT && \
+ cp -r /build/assemblies/web/target/webapp/*
/build/hop-web-prepared/webapps/ROOT/ && \
+ cp -r /build/assemblies/client/target/hop/config
/build/hop-web-prepared/webapps/ROOT/ && \
+ cp -r /build/assemblies/client/target/hop/plugins /build/hop-web-prepared/
&& \
+ cp -r /build/assemblies/client/target/hop/lib/jdbc/
/build/hop-web-prepared/jdbc-drivers && \
+ cp -r /build/assemblies/client/target/hop/lib/beam/*
/build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/ && \
+ cp -r /build/assemblies/client/target/hop/lib/core/*
/build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/ && \
+ rm /build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/hop-ui-rcp* && \
+ mkdir -p /build/hop-web-prepared/bin && \
+ cp -r /build/docker/resources/run-web.sh
/build/hop-web-prepared/bin/run-web.sh
+
+# Make scripts executable
+RUN chmod +x /build/hop-web-prepared/webapps/ROOT/*.sh \
+ && chmod +x /build/hop-web-prepared/bin/run-web.sh
+
+ # Fix hop-config.json
+RUN sed -i 's/config\/projects/${HOP_CONFIG_FOLDER}\/projects/g'
/build/hop-web-prepared/webapps/ROOT/config/hop-config.json
+
+# Set the correct classpath for hop scripts
+RUN sed -i 's&lib/core/*&../../lib/*:WEB-INF/lib/*:lib/core/*&g'
/build/hop-web-prepared/webapps/ROOT/hop-run.sh
+RUN sed -i 's&lib/core/*&../../lib/*:WEB-INF/lib/*:lib/core/*&g'
/build/hop-web-prepared/webapps/ROOT/hop-conf.sh
+RUN sed -i 's&lib/core/*&../../lib/*:WEB-INF/lib/*:lib/core/*&g'
/build/hop-web-prepared/webapps/ROOT/hop-search.sh
+RUN sed -i 's&lib/core/*&../../lib/*:WEB-INF/lib/*:lib/core/*&g'
/build/hop-web-prepared/webapps/ROOT/hop-encrypt.sh
+RUN sed -i 's&lib/core/*&../../lib/*:WEB-INF/lib/*:lib/core/*&g'
/build/hop-web-prepared/webapps/ROOT/hop-import.sh
+RUN sed -i 's&lib/core/*&../../lib/*:WEB-INF/lib/*:lib/core/*&g'
/build/hop-web-prepared/webapps/ROOT/hop-search.sh
+
+
+# Prepare Hop Client directory structure
+RUN mkdir -p /build/hop-client-prepared && \
+ # Copy entire hop installation
+ cp -r /build/assemblies/client/target/hop/* /build/hop-client-prepared/ &&
\
+ # Copy run scripts
+ cp /build/docker/resources/run.sh /build/hop-client-prepared/run.sh && \
+ cp /build/docker/resources/load-and-execute.sh
/build/hop-client-prepared/load-and-execute.sh && \
+ chmod +x /build/hop-client-prepared/run.sh
/build/hop-client-prepared/load-and-execute.sh
+
+# Prepare Hop REST directory structure
+RUN mkdir -p /build/hop-rest-prepared/plugins && \
+ mkdir -p /build/hop-rest-prepared/webapps && \
+ mkdir -p /build/hop-rest-prepared/lib/swt/linux/x86_64 && \
+ mkdir -p /build/hop-rest-prepared/bin && \
+ # Copy plugins
+ cp -r /build/assemblies/plugins/target/plugins/*
/build/hop-rest-prepared/plugins/ && \
+ # Copy REST war
+ cp /build/rest/target/hop-rest*.war
/build/hop-rest-prepared/webapps/hop.war && \
+ # Copy run script
+ cp /build/docker/resources/run-rest.sh
/build/hop-rest-prepared/bin/run-rest.sh && \
+ chmod +x /build/hop-rest-prepared/bin/run-rest.sh
+
+################################################################################
+# Stage 4a: Hop Client/Server Image (Standard)
+################################################################################
+FROM alpine:latest AS client
+
+# Build arguments
+ARG HOP_UID=501
+ARG HOP_GID=501
+
+# Environment variables
+ENV DEPLOYMENT_PATH=/opt/hop
+ENV VOLUME_MOUNT_POINT=/files
+ENV HOP_LOG_LEVEL=Basic
+ENV HOP_FILE_PATH=
+ENV HOP_LOG_PATH=${DEPLOYMENT_PATH}/hop.err.log
+ENV HOP_SHARED_JDBC_FOLDERS=
+ENV HOP_PROJECT_NAME=
+ENV HOP_PROJECT_DIRECTORY=
+ENV HOP_PROJECT_FOLDER=
+ENV HOP_PROJECT_CONFIG_FILE_NAME=project-config.json
+ENV HOP_ENVIRONMENT_NAME=
+ENV HOP_ENVIRONMENT_CONFIG_FILE_NAME_PATHS=
+ENV HOP_RUN_CONFIG=
+ENV HOP_RUN_PARAMETERS=
+ENV HOP_START_ACTION=
+ENV HOP_RUN_METADATA_EXPORT=
+ENV HOP_SYSTEM_PROPERTIES=
+ENV HOP_OPTIONS=-XX:+AggressiveHeap
+ENV HOP_CUSTOM_ENTRYPOINT_EXTENSION_SHELL_FILE_PATH=
+ENV HOP_SERVER_USER=cluster
+ENV HOP_SERVER_PASSWORD=cluster
+ENV HOP_SERVER_HOSTNAME=0.0.0.0
+ENV HOP_SERVER_PORT=8080
+ENV HOP_SERVER_SHUTDOWNPORT=8079
+ENV HOP_SERVER_METADATA_FOLDER=
+ENV HOP_SERVER_KEYSTORE=
+ENV HOP_SERVER_KEYSTORE_PASSWORD=
+ENV HOP_SERVER_KEY_PASSWORD=
+ENV HOP_SERVER_MAX_LOG_LINES=
+ENV HOP_SERVER_MAX_LOG_TIMEOUT=
+ENV HOP_SERVER_MAX_OBJECT_TIMEOUT=
+ENV HOP_CONFIG_OPTIONS=
+
+# Install required packages
+RUN addgroup -g ${HOP_GID} -S hop \
+ && adduser -u ${HOP_UID} -S -D -G hop hop \
+ && chmod 777 -R /tmp && chmod o+t -R /tmp \
+ && apk update \
+ && apk --no-cache add bash curl fontconfig msttcorefonts-installer
openjdk17-jre procps \
+ && update-ms-fonts \
+ && fc-cache -f \
+ && rm -rf /var/cache/apk/* \
+ && mkdir ${DEPLOYMENT_PATH} \
+ && mkdir ${VOLUME_MOUNT_POINT} \
+ && chown hop:hop ${DEPLOYMENT_PATH} \
+ && chown hop:hop ${VOLUME_MOUNT_POINT}
+
+# Copy pre-assembled Hop Client structure from builder (SINGLE layer!)
+COPY --from=builder --chown=hop:hop /build/hop-client-prepared/
${DEPLOYMENT_PATH}/
+
+# Expose ports
+EXPOSE 8080 8079
+
+# Configure
+VOLUME ["/files"]
+USER hop
+ENV PATH=${PATH}:${DEPLOYMENT_PATH}/hop
+WORKDIR /home/hop
+
+CMD bash -c "$DEPLOYMENT_PATH/run.sh"
+
+################################################################################
+# Stage 4b: Hop Web Image
+################################################################################
+FROM tomcat:10-jdk17 AS web
+
+# Build arguments
+ARG HOP_UID=501
+ARG HOP_GID=501
+
+# Environment variables
+ENV DEPLOYMENT_PATH=/usr/local/tomcat/webapps/ROOT
+ENV HOP_AES_ENCODER_KEY=""
+ENV HOP_AUDIT_FOLDER="${CATALINA_HOME}/webapps/ROOT/audit"
+ENV HOP_CONFIG_FOLDER="${CATALINA_HOME}/webapps/ROOT/config"
+ENV HOP_LOG_LEVEL="Basic"
+ENV HOP_OPTIONS="-XX:+AggressiveHeap
-Dorg.eclipse.rap.rwt.resourceLocation=/tmp/rwt-resources"
+ENV HOP_PASSWORD_ENCODER_PLUGIN="Hop"
+ENV HOP_PLUGIN_BASE_FOLDERS=${CATALINA_HOME}/plugins
+ENV HOP_SHARED_JDBC_FOLDERS="${CATALINA_HOME}/jdbc-drivers"
+ENV HOP_WEB_THEME="light"
+ENV HOP_GUI_ZOOM_FACTOR=1.0
+ENV HOP_PROJECT_FOLDER=
+ENV HOP_PROJECT_CONFIG_FILE_NAME=project-config.json
+ENV HOP_ENVIRONMENT_NAME=environment1
+ENV HOP_ENVIRONMENT_CONFIG_FILE_NAME_PATHS=
+
+# Set TOMCAT start variables
+ENV CATALINA_OPTS='${HOP_OPTIONS} \
+ -DHOP_AES_ENCODER_KEY="${HOP_AES_ENCODER_KEY}" \
+ -DHOP_AUDIT_FOLDER="${HOP_AUDIT_FOLDER}" \
+ -DHOP_CONFIG_FOLDER="${HOP_CONFIG_FOLDER}" \
+ -DHOP_LOG_LEVEL="${HOP_LOG_LEVEL}" \
+ -DHOP_PASSWORD_ENCODER_PLUGIN="${HOP_PASSWORD_ENCODER_PLUGIN}" \
+ -DHOP_PLUGIN_BASE_FOLDERS="${HOP_PLUGIN_BASE_FOLDERS}" \
+ -DHOP_SHARED_JDBC_FOLDERS="${HOP_SHARED_JDBC_FOLDERS}" \
+ -DHOP_WEB_THEME="${HOP_WEB_THEME}" \
+ -DHOP_GUI_ZOOM_FACTOR="${HOP_GUI_ZOOM_FACTOR}"'
+
+# Create Hop user
+RUN groupadd -r hop -g ${HOP_GID} \
+ && useradd -d /home/hop -u ${HOP_UID} -m -s /bin/bash -g hop hop \
+ && rm -rf webapps/* \
+ && mkdir "${CATALINA_HOME}"/webapps/ROOT \
+ && mkdir "${HOP_AUDIT_FOLDER}" \
+ && chown -R hop:hop /usr/local/tomcat
+
+# Copy resources (matching original Dockerfile.web layer structure)
+COPY --from=builder --chown=hop /build/hop-web-prepared/ "${CATALINA_HOME}"
+
+USER hop
+
+CMD bash -c "$CATALINA_HOME/bin/run-web.sh"
+
+
+################################################################################
+# Stage 4c: Hop Dataflow Template Image
+################################################################################
+FROM gcr.io/dataflow-templates-base/java17-template-launcher-base AS dataflow
+
+ARG WORKDIR=/dataflow/template
+RUN mkdir -p ${WORKDIR}
+WORKDIR ${WORKDIR}
+
+# Copy fat jar from builder
+COPY --from=builder /tmp/hop-fatjar.jar ./
+
+ENV
FLEX_TEMPLATE_JAVA_MAIN_CLASS="org.apache.hop.beam.run.MainDataflowTemplate"
+ENV FLEX_TEMPLATE_JAVA_CLASSPATH="${WORKDIR}/*"
+
+ENTRYPOINT ["/opt/google/dataflow/java_template_launcher"]
+
+################################################################################
+# Image Variants/Flavors
+################################################################################
+# These stages extend base stages to create variants with additional features
+# Stage names follow pattern: <base>-<variant> (e.g., web-beam, client-minimal)
+################################################################################
+
+################################################################################
+# Stage 4e: Hop Web with Beam (includes fat jar for Dataflow)
+################################################################################
+FROM web AS web-beam
+LABEL variant="beam"
+
+# Copy fat jar from builder into web image for Dataflow integration
+COPY --from=builder /tmp/hop-fatjar.jar /root/hop-fatjar.jar
\ No newline at end of file
diff --git a/docker/Dockerfile.web-fatjar b/docker/web-fatjar.Dockerfile
similarity index 100%
rename from docker/Dockerfile.web-fatjar
rename to docker/web-fatjar.Dockerfile
diff --git a/docker/Dockerfile.web b/docker/web.Dockerfile
similarity index 100%
rename from docker/Dockerfile.web
rename to docker/web.Dockerfile
diff --git a/docs/hop-dev-manual/modules/ROOT/nav.adoc
b/docs/hop-dev-manual/modules/ROOT/nav.adoc
index 5c4a869f9c..c81487ca81 100644
--- a/docs/hop-dev-manual/modules/ROOT/nav.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/nav.adoc
@@ -19,6 +19,7 @@ under the License.
* xref:porting-kettle-plugins.adoc[Porting Kettle plugins]
* xref:metadata-plugins.adoc[Metadata plugins]
* xref:setup-dev-environment.adoc[Setting up your development environment]
+* xref:docker-build.adoc[Docker Build Script]
* xref:integration-testing.adoc[Integration testing]
* xref:internationalisation.adoc[Internationalisation (i18n)]
* xref:plugin-development.adoc[Plugins Development]
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/docker-build.adoc
b/docs/hop-dev-manual/modules/ROOT/pages/docker-build.adoc
new file mode 100644
index 0000000000..5ebcdf9b46
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/docker-build.adoc
@@ -0,0 +1,498 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+////
+:description: The Apache Hop Docker Build Script provides a unified way to
build all Hop container images using multi-stage Docker builds.
+
+[[docker-build]]
+= Docker Build Script
+
+The Apache Hop project provides a unified build script (`build-hop-images.sh`)
that can build all Apache Hop Docker images using a multi-stage build approach.
This script supports building from local source or from a GitHub tag, with
options for multi-platform builds and registry pushing.
+
+== Overview
+
+The build script creates the following Docker images:
+
+* **hop** (client) - The main Apache Hop client/server image based on Alpine
Linux
+* **hop-web** - Apache Hop Web interface running on Tomcat
+* **hop-web-beam** - Hop Web variant with Apache Beam fat jar for Google Cloud
Dataflow integration
+* **hop-dataflow-template** - Google Cloud Dataflow Flex Template image
+
+== Build Architecture
+
+The build process uses a unified multi-stage Dockerfile that shares common
build stages across all images, reducing build time and ensuring consistency.
+
+[mermaid]
+....
+flowchart TB
+ subgraph Source["Stage 1: Source Preparation"]
+ SG[source-github<br/>Clone from GitHub]
+ SL[Local Source<br/>From build context]
+ end
+
+ subgraph Builder["Stage 2: Builder"]
+ BF[builder-full<br/>Maven build from source]
+ BFast[builder-fast<br/>Pre-built artifacts]
+ end
+
+ subgraph Prep["Stage 3: Preparation"]
+ BP[builder<br/>Extract & prepare artifacts<br/>Generate fat jar]
+ end
+
+ subgraph Final["Stage 4: Final Images"]
+ IC["client<br/>Alpine + JRE<br/><registry>/hop:<version>"]
+ IW["web<br/>Tomcat<br/><registry>/hop-web:<version>"]
+ IWB["web-beam<br/>Tomcat + fat
jar<br/><registry>/hop-web:<version>-beam"]
+ ID["dataflow<br/>GCP Dataflow
base<br/><registry>/hop-dataflow-template:<version>"]
+ end
+
+ SG --> BF
+ SL --> BFast
+ BF --> BP
+ BFast --> BP
+ BP --> IC
+ BP --> IW
+ BP --> ID
+ IW --> IWB
+
+ style SG fill:#e1f5fe
+ style SL fill:#e1f5fe
+ style BF fill:#fff3e0
+ style BFast fill:#fff3e0
+ style BP fill:#f3e5f5
+ style IC fill:#e8f5e9
+ style IW fill:#e8f5e9
+ style IWB fill:#c8e6c9
+ style ID fill:#e8f5e9
+....
+
+== Prerequisites
+
+Before using the build script, ensure you have:
+
+* **Docker** - Version 20.10 or higher
+* **Docker Buildx** - Required only for multi-platform builds (usually
included with Docker Desktop)
+* **Maven** - Only if building locally before using `--builder fast`
+
+== Command Line Arguments
+
+The build script supports the following arguments:
+
+[cols="2,1,3,2", options="header"]
+|===
+|Argument |Short |Description |Default
+
+|`--source <type>`
+|`-s`
+|Build from `local` source or `github`
+|`local`
+
+|`--tag <tag>`
+|`-t`
+|Git tag or branch to build from (when using `--source github`)
+|`main`
+
+|`--repo <url>`
+|`-r`
+|GitHub repository URL
+|`https://github.com/apache/hop.git`
+
+|`--images <list>`
+|`-i`
+|Comma-separated list of images to build, or `all`
+|`all`
+
+|`--version <version>`
+|`-v`
+|Version string for image tagging
+|Auto-detected from pom.xml
+
+|`--push`
+|`-p`
+|Push images to registry after build
+|`false`
+
+|`--registry <registry>`
+|`-x`
+|Docker registry prefix (e.g., `apache`, `myregistry.io/myorg`)
+|None (local only)
+
+|`--platforms <platforms>`
+|
+|Build platforms (e.g., `linux/amd64,linux/arm64`)
+|Current system platform
+
+|`--maven-threads <threads>`
+|
+|Maven build parallelism (e.g., `1C`, `2C`, `4`)
+|`1C` (1 thread per CPU core)
+
+|`--progress <mode>`
+|
+|Docker build output: `auto`, `plain` (verbose), `tty` (compact)
+|`auto`
+
+|`--builder <type>`
+|
+|Builder type: `full` (Maven build) or `fast` (pre-built artifacts)
+|`full`
+
+|`--no-cache`
+|
+|Build without using Docker cache
+|Cache enabled
+
+|`--help`
+|`-h`
+|Show help message
+|
+|===
+
+== Available Image Stages
+
+The following image stages can be specified with the `--images` argument:
+
+[cols="1,2,3", options="header"]
+|===
+|Stage |Image Name |Description
+
+|`client`
+|`hop`
+|Main Hop client/server image. Based on Alpine Linux with OpenJDK 17. Can run
pipelines, workflows, and Hop Server.
+
+|`web`
+|`hop-web`
+|Hop Web interface running on Apache Tomcat 10. Provides browser-based access
to the Hop GUI.
+
+|`web-beam`
+|`hop-web` (variant)
+|Hop Web with Apache Beam fat jar included. Use for Google Cloud Dataflow
integration. Tagged with `-beam` suffix.
+
+|`dataflow`
+|`hop-dataflow-template`
+|Google Cloud Dataflow Flex Template image. Contains only the fat jar for
running Hop pipelines on Dataflow.
+|===
+
+== Builder Types
+
+=== Full Builder (Default)
+
+The `full` builder performs a complete Maven build from source:
+
+[source,bash]
+----
+./build-hop-images.sh --builder full
+----
+
+* Clones source code
+* Runs full Maven build with all dependencies
+* Slower but ensures everything is built from scratch
+* Required for CI/CD and release builds
+
+=== Fast Builder
+
+The `fast` builder uses pre-built artifacts, skipping Maven:
+
+[source,bash]
+----
+# First, build with Maven locally
+mvn clean install -DskipTests
+
+# Then build Docker images using pre-built artifacts (only Hop-web in this
example)
+./build-hop-images.sh --builder fast --images web
+----
+
+* Requires `mvn clean install` to be run first
+* Much faster for local development iteration
+* Copies artifacts directly from `target/` folders
+
+== Image Tagging
+
+Images are automatically tagged based on the version:
+
+* **SNAPSHOT versions** (e.g., `2.17.0-SNAPSHOT`):
+** Primary tag: `hop-web:2.17.0-SNAPSHOT`
+** Alias tag: `hop-web:Development`
+
+* **Release versions** (e.g., `2.17.0`):
+** Primary tag: `hop-web:2.17.0`
+** Alias tag: `hop-web:latest`
+
+* **Variant images** get suffix added:
+** Primary tag: `hop-web:2.17.0-SNAPSHOT-beam`
+** Alias tag: `hop-web:Development-beam`
+
+== Examples
+
+=== Basic Usage
+
+Build all images from local source:
+
+[source,bash]
+----
+./build-hop-images.sh
+----
+
+=== Build Specific Images
+
+Build only the web and client images:
+
+[source,bash]
+----
+./build-hop-images.sh --images client,web
+----
+
+=== Build from GitHub Release
+
+Build version 2.9.0 from GitHub:
+
+[source,bash]
+----
+./build-hop-images.sh --source github --tag 2.9.0
+----
+
+=== Fast Development Build
+
+For quick iteration during development:
+
+[source,bash]
+----
+# Build project with Maven first
+mvn clean install -DskipTests
+
+# Fast Docker build (skips Maven, uses pre-built artifacts)
+./build-hop-images.sh --builder fast --images web
+----
+
+=== Multi-Platform Build with Push
+
+Build for AMD64 and ARM64, then push to Docker Hub:
+
+[source,bash]
+----
+./build-hop-images.sh \
+ --platforms linux/amd64,linux/arm64 \
+ --push \
+ --registry apache \
+ --version 2.9.0
+----
+
+=== Build Web with Beam Variant
+
+Build the web image with Apache Beam fat jar for Dataflow:
+
+[source,bash]
+----
+./build-hop-images.sh --images web-beam --registry myregistry
+----
+
+This creates:
+
+* `myregistry/hop-web:2.17.0-SNAPSHOT-beam`
+* `myregistry/hop-web:Development-beam`
+
+=== Verbose Build Output
+
+See detailed build progress:
+
+[source,bash]
+----
+./build-hop-images.sh --progress plain --images client
+----
+
+=== Build Without Cache
+
+Force a fresh build without using cached layers:
+
+[source,bash]
+----
+./build-hop-images.sh --no-cache --images web
+----
+
+== Configuration File
+
+You can create a `build.env` file in the `docker/` directory to set default
values:
+
+[source,bash]
+----
+# docker/build.env
+REGISTRY=myregistry.io/myorg
+PLATFORMS=linux/amd64,linux/arm64
+PUSH=true
+MAVEN_THREADS=2C
+----
+
+Command line arguments override values from `build.env`.
+
+== Troubleshooting
+
+=== Build Fails with "pom.xml not found"
+
+Ensure you're running the script from the repository root or the `docker/`
directory:
+
+[source,bash]
+----
+cd /path/to/hop
+./docker/build-hop-images.sh
+----
+
+=== Multi-Platform Build Fails
+
+Multi-platform builds require Docker Buildx:
+
+[source,bash]
+----
+# Check if buildx is available
+docker buildx version
+
+# Create a builder if needed
+docker buildx create --use --name hop-builder
+----
+
+=== Out of Memory During Maven Build
+
+Increase Maven memory by setting threads lower:
+
+[source,bash]
+----
+./build-hop-images.sh --maven-threads 1
+----
+
+=== Images Not Appearing Locally After Multi-Platform Build
+
+Multi-platform builds require `--push` or won't load locally. For local
testing, use single platform:
+
+[source,bash]
+----
+# Single platform (loads locally)
+./build-hop-images.sh --platforms linux/amd64
+
+# Multi-platform requires push
+./build-hop-images.sh --platforms linux/amd64,linux/arm64 --push --registry
myregistry
+----
+
+== Adding a New Image Variant
+
+You can extend the build system by adding new image variants. Variants are
images that extend a base image with additional features. For example,
`web-beam` extends the `web` image with the Apache Beam fat jar.
+
+This section demonstrates how to add a hypothetical `client-debug` variant
that includes additional debugging tools.
+
+=== Step 1: Add the Stage to unified.Dockerfile
+
+Add a new stage at the end of `docker/unified.Dockerfile` that extends the
base image:
+
+[source,dockerfile]
+----
+################################################################################
+# Stage: Hop Client with Debug Tools
+################################################################################
+FROM client AS client-debug
+LABEL variant="debug"
+
+# Switch to root to install packages
+USER root
+
+# Install debugging tools
+RUN apk add --no-cache \
+ strace \
+ htop \
+ vim \
+ curl \
+ netcat-openbsd
+
+# Add debug-specific environment variables
+ENV HOP_OPTIONS="-XX:+AggressiveHeap
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
+
+# Expose debug port
+EXPOSE 5005
+
+# Switch back to hop user
+USER hop
+----
+
+Key points:
+
+* **Stage name format**: Use `<base>-<variant>` naming (e.g., `client-debug`)
+* **FROM clause**: Extend the base image (`FROM client AS client-debug`)
+* **LABEL**: Add `variant="debug"` label for identification
+* **Inheritance**: The variant inherits everything from the base image
+
+=== Step 2: Register the Variant in build-hop-images.sh
+
+Add the new variant to the `ALL_STAGES` array at the top of
`docker/build-hop-images.sh`:
+
+[source,bash]
+----
+# Available image stages (add new stages here)
+# Format: "baseImage" or "baseImage-variant"
+ALL_STAGES=("client" "client-debug" "web" "web-beam" "dataflow")
+----
+
+The build script automatically handles:
+
+* **Image naming**: `client-debug` → image name `hop` (from base `client`)
+* **Tag suffix**: Version tag gets `-debug` suffix (e.g.,
`hop:2.17.0-SNAPSHOT-debug`)
+* **Alias tags**: Development/latest tags also get suffix (e.g.,
`hop:Development-debug`)
+
+=== Step 3: Build and Test the Variant
+
+Build only the new variant:
+
+[source,bash]
+----
+./build-hop-images.sh --images client-debug --registry myregistry
+----
+
+This produces:
+
+* `myregistry/hop:2.17.0-SNAPSHOT-debug`
+* `myregistry/hop:Development-debug`
+
+Build all images including the new variant:
+
+[source,bash]
+----
+./build-hop-images.sh --images all
+----
+
+=== How Variant Detection Works
+
+The build script uses these functions to handle variants:
+
+[source,bash]
+----
+# Extracts base image name from stage name
+get_image_name() {
+ local stage_name="$1"
+ case "$stage_name" in
+ client*) echo "hop" ;;
+ web*) echo "hop-web" ;;
+ dataflow*) echo "hop-dataflow-template" ;;
+ *) echo "" ;;
+ esac
+}
+
+# Extracts variant suffix (everything after first hyphen)
+get_variant_suffix() {
+ local stage_name="$1"
+ if [[ "$stage_name" == *"-"* ]]; then
+ echo "${stage_name#*-}" # Returns "debug" from "client-debug"
+ else
+ echo ""
+ fi
+}
+----
+
+For a new base image type (not a variant of existing), you would also need to
update the `get_image_name()` function.
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/index.adoc
b/docs/hop-dev-manual/modules/ROOT/pages/index.adoc
index f4ef9727d1..4afeabf2cd 100644
--- a/docs/hop-dev-manual/modules/ROOT/pages/index.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/pages/index.adoc
@@ -20,6 +20,7 @@ under the License.
* xref:metadata-plugins.adoc[Metadata plugins]
* xref:porting-kettle-plugins.adoc[Porting Kettle plugins]
* xref:setup-dev-environment.adoc[Setting up your development environment]
+* xref:docker-build.adoc[Docker Build Script]
* xref:integration-testing.adoc[Integration Testing]
* xref:plugin-development.adoc[Plugin Development]
* xref:plugin-samples.adoc[Plugin Samples]
diff --git a/integration-tests/samples/read-samples-build-hop-run.hpl
b/integration-tests/samples/read-samples-build-hop-run.hpl
index ca8be57ac2..88f53e4a8b 100644
--- a/integration-tests/samples/read-samples-build-hop-run.hpl
+++ b/integration-tests/samples/read-samples-build-hop-run.hpl
@@ -640,7 +640,7 @@ var hop_run_cmd = './hop-run.sh -j samples -r ' +
run_config + ' -f ' + filename
<item>Is intended to fail.</item>
</line>
<line>
- <item>sample_pipeline_resolver</item>
+ <item>sample_pipeline_resolver.hpl</item>
<item>Is used by another transform</item>
</line>
</data>