diff --git a/.github/workflows/sonar-branch.yml b/.github/workflows/sonar-branch.yml
index f8010dca4..b4cb18f2e 100644
--- a/.github/workflows/sonar-branch.yml
+++ b/.github/workflows/sonar-branch.yml
@@ -16,11 +16,11 @@ jobs:
       - uses: actions/checkout@v3
         with:
           fetch-depth: 0
-      - name: Setup JDK 11
-        uses: actions/setup-java@v3
+      - name: Setup JDK
+        uses: actions/setup-java@v4
         with:
           distribution: 'adopt'
-          java-version: 11
+          java-version: 17
       - name: Build and Test
         run: |
           mvn -B clean package
diff --git a/documentation/site/content/userguide/tools/create-aux-image.md b/documentation/site/content/userguide/tools/create-aux-image.md
index a6fb7caed..abe6c8b29 100644
--- a/documentation/site/content/userguide/tools/create-aux-image.md
+++ b/documentation/site/content/userguide/tools/create-aux-image.md
@@ -32,6 +32,7 @@ Usage: imagetool createAuxImage [OPTIONS]
 | `--httpProxyUrl` | Proxy for the HTTP protocol. Example: `http://myproxy:80` or `http:user:passwd@myproxy:8080`  |   |
 | `--httpsProxyUrl` | Proxy for the HTTPS protocol. Example: `https://myproxy:80` or `https:user:passwd@myproxy:8080`  |   |
 | `--packageManager` | Override the default package manager for the base image's operating system. Supported values: `APK`, `APTGET`, `NONE`, `YUM`, `ZYPPER`  |   |
+| `--platform` | Set the target platform to build.  Supported values: `linux/amd64` or `linux/arm64`. |   |
 | `--pull` | Always attempt to pull a newer version of base images during the build.  |   |
 | `--skipcleanup` | Do not delete the build context folder, intermediate images, and failed build containers. For debugging purposes.  |   |
 | `--target` | Select the target environment in which the created image will be used. Supported values: `Default` (Docker/Kubernetes), `OpenShift`. See [Additional information](#--target). | `Default`  |
diff --git a/documentation/site/content/userguide/tools/create-image.md b/documentation/site/content/userguide/tools/create-image.md
index 307955c84..43ba340bb 100644
--- a/documentation/site/content/userguide/tools/create-image.md
+++ b/documentation/site/content/userguide/tools/create-image.md
@@ -40,6 +40,7 @@ Usage: imagetool create [OPTIONS]
 | `--passwordEnv` | Environment variable containing the Oracle Support password, see `--user`.  |   |
 | `--passwordFile` | Path to a file containing just the Oracle Support password, see `--user`.  |   |
 | `--patches` | Comma separated list of patch IDs. Example: `12345678,87654321`  |   |
+| `--platform` | Set the target platform to build.  Supported values: `linux/amd64` or `linux/arm64`. |   |
 | `--pull` | Always attempt to pull a newer version of base images during the build.  |   |
 | `--recommendedPatches` | Find and apply the latest PatchSet Update and recommended patches. This takes precedence over `--latestPSU`.  |   |
 | `--resourceTemplates` | One or more files containing placeholders that need to be resolved by the Image Tool. See [Resource Template Files](#resource-template-files). |   |
diff --git a/documentation/site/content/userguide/tools/rebase-image.md b/documentation/site/content/userguide/tools/rebase-image.md
index 417a93652..7b279ec58 100644
--- a/documentation/site/content/userguide/tools/rebase-image.md
+++ b/documentation/site/content/userguide/tools/rebase-image.md
@@ -39,6 +39,7 @@ Usage: imagetool rebase [OPTIONS]
 | `--passwordEnv` | Environment variable containing the Oracle Support password, see `--user`.  |   |
 | `--passwordFile` | Path to a file containing just the Oracle Support password, see `--user`.  |   |
 | `--patches` | Comma separated list of patch IDs. Example: `12345678,87654321`  |   |
+| `--platform` | Set the target platform to build.  Supported values: `linux/amd64` or `linux/arm64`. |   |
 | `--pull` | Always attempt to pull a newer version of base images during the build.  |   |
 | `--recommendedPatches` | Find and apply the latest PatchSet Update and recommended patches. This takes precedence over `--latestPSU`. |   |
 | `--skipcleanup` | Do not delete the build context folder, intermediate images, and failed build containers. For debugging purposes.  |   |
diff --git a/documentation/site/content/userguide/tools/update-image.md b/documentation/site/content/userguide/tools/update-image.md
index bfeabaa7e..4a6e66fd2 100644
--- a/documentation/site/content/userguide/tools/update-image.md
+++ b/documentation/site/content/userguide/tools/update-image.md
@@ -41,6 +41,7 @@ Update WebLogic Docker image with selected patches
 | `--passwordEnv` | Environment variable containing the Oracle Support password, see `--user`. |  |
 | `--passwordFile` | Path to a file containing just the Oracle Support password, see `--user`.  |  |
 | `--patches` | Comma separated list of patch IDs. Example: `12345678,87654321` |  |
+| `--platform` | Set the target platform to build.  Supported values: `linux/amd64` or `linux/arm64`. |   |
 | `--pull` | Always attempt to pull a newer version of base images during the build. | |
 | `--recommendedPatches` | (DEPRECATED) Find and apply the latest PatchSet Update and recommended patches. This takes precedence over `--latestPSU`. See [Additional information](#--recommendedpatches). |  |
 | `--resourceTemplates` | One or more files containing placeholders that need to be resolved by the Image Tool. See [Resource Template Files](#resource-template-files). |  |
diff --git a/imagetool/pom.xml b/imagetool/pom.xml
index b3f509ce1..d41329d5e 100644
--- a/imagetool/pom.xml
+++ b/imagetool/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <artifactId>imagetool-parent</artifactId>
         <groupId>com.oracle.weblogic.lifecycle.imagetool</groupId>
-        <version>1.12.2</version>
+        <version>1.13.0</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/api/model/CachedFile.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/api/model/CachedFile.java
index 03bd84b63..622a7f0b4 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/api/model/CachedFile.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/api/model/CachedFile.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2021, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.api.model;
@@ -15,6 +15,7 @@
 import com.oracle.weblogic.imagetool.installer.InstallerType;
 import com.oracle.weblogic.imagetool.logging.LoggingFacade;
 import com.oracle.weblogic.imagetool.logging.LoggingFactory;
+import com.oracle.weblogic.imagetool.util.BuildPlatform;
 import com.oracle.weblogic.imagetool.util.Utils;
 
 /**
@@ -24,23 +25,47 @@ public class CachedFile {
 
     private static final LoggingFacade logger = LoggingFactory.getLogger(CachedFile.class);
 
-    private String id;
-    private String version;
+    private final String id;
+    private final String version;
+    private final String architecture;
 
     /**
      * Represents a locally cached file.
      *
-     * @param id          cache ID (like installer type or patchId)
-     * @param version     version number for the patch or installer.
+     * @param id           cache ID (like installer type or patchId)
+     * @param version      version number for the patch or installer.
+     * @param architecture the system architecture that this file/installer is applicable
      */
-    public CachedFile(String id, String version) {
+    public CachedFile(String id, String version, String architecture) {
         Objects.requireNonNull(id, "key for the cached file cannot be null");
-        logger.entering(id, version);
+        logger.entering(id, version, architecture);
         this.id = id;
         this.version = version;
+        this.architecture = architecture;
         logger.exiting();
     }
 
+    /**
+     * Represents a locally cached file.
+     *
+     * @param id          cache ID (like installer type or patchId)
+     * @param version     version number for the patch or installer.
+     */
+    public CachedFile(String id, String version) {
+        this(id, version, null);
+    }
+
+    /**
+     * Represents a locally cached file.
+     *
+     * @param id           cache ID (like installer type)
+     * @param version      version number for the patch or installer.
+     * @param architecture the system architecture that this file/installer is applicable
+     */
+    public CachedFile(InstallerType id, String version, String architecture) {
+        this(id.toString(), version, architecture);
+    }
+
     /**
      * Represents a locally cached file.
      *
@@ -48,7 +73,7 @@ public CachedFile(String id, String version) {
      * @param version     version number for the patch or installer.
      */
     public CachedFile(InstallerType id, String version) {
-        this(id.toString(), version);
+        this(id.toString(), version, null);
     }
 
     public static boolean isFileOnDisk(String filePath) {
@@ -62,11 +87,23 @@ public static boolean isFileOnDisk(String filePath) {
      * @return the key to use for this cache entry, like xxxx_yyyy.
      */
     public String getKey() {
+        return getCacheKey(architecture);
+    }
+
+    private String getCacheKey(String architecture) {
         if (id.contains(CacheStore.CACHE_KEY_SEPARATOR)) {
             return id;
-        } else {
-            return id + CacheStore.CACHE_KEY_SEPARATOR + getVersion();
         }
+
+        StringBuilder key = new StringBuilder(32);
+        key.append(id);
+        key.append(CacheStore.CACHE_KEY_SEPARATOR);
+        key.append(version);
+        if (architecture != null) {
+            key.append(CacheStore.CACHE_KEY_SEPARATOR);
+            key.append(architecture);
+        }
+        return key.toString();
     }
 
     /**
@@ -77,8 +114,22 @@ public String getVersion() {
         return version;
     }
 
+    /**
+     * Get the system architecture name for this cache entry/file.
+     * @return the system architecture name applicable fo this cached file.
+     */
+    public String getArchitecture() {
+        return architecture;
+    }
+
     /**
      * Get the path of the file stored locally in the cache.
+     * Searching the cache starts with the specified key.  If the key is not found in the cache,
+     * one additional attempt is made to find an acceptable alternative.  The second search is based on
+     * whether the user specified a platform/architecture.  If the user specified an architecture, check the cache
+     * for an entry listing without the architecture in the key (generic architecture entry).  If the user
+     * did not specify an architecture, check the cache for an entry listing using the local architecture
+     * in case the user added the cache entry with the architecture.
      * @param cacheStore the cache store to search
      * @return the Path of the file, if found
      * @throws IOException throws FileNotFoundException, if this cached file (key) could not be located in the cache
@@ -88,6 +139,25 @@ public String resolve(CacheStore cacheStore) throws IOException {
         String key = getKey();
         logger.entering(key);
         String filePath = cacheStore.getValueFromCache(key);
+        if (filePath == null) {
+            // The KEY for this CachedFile was not found in the local cache.
+            logger.fine("Unable to find cache entry for {0}", key);
+            String alternateKey;
+            if (getArchitecture() == null) {
+                // The user did not specify an architecture in the KEY and that key was not found in the cache.
+                // Try adding the local architecture to the key, and look for that entry.
+                alternateKey = getCacheKey(BuildPlatform.getPlatformName());
+                logger.fine("Trying local architecture: {0}", alternateKey);
+            } else {
+                // The user specified an architecture in the KEY, but that key was not found.
+                // Try removing the architecture from the key, and look for that entry.
+                alternateKey = getCacheKey(null);
+                logger.fine("Trying no-arch/generic architecture: {0}", alternateKey);
+            }
+            // second attempt to find a reasonable cache entry
+            filePath = cacheStore.getValueFromCache(alternateKey);
+        }
+
         if (!isFileOnDisk(filePath)) {
             throw new FileNotFoundException(Utils.getMessage("IMG-0011", key));
         }
@@ -103,7 +173,7 @@ public String resolve(CacheStore cacheStore) throws IOException {
      * @return the path of the file copied to the Docker build context directory
      */
     public Path copyFile(CacheStore cacheStore, String buildContextDir) throws IOException {
-        logger.entering();
+        logger.entering(id, version, architecture, buildContextDir);
         Path result;
         String sourceFile = resolve(cacheStore);
         logger.info("IMG-0043", sourceFile);
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/AruPatch.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/AruPatch.java
index 298baf1b7..a03eae7cd 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/AruPatch.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/AruPatch.java
@@ -1,11 +1,10 @@
-// Copyright (c) 2020, 2022, Oracle and/or its affiliates.
+// Copyright (c) 2020, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.aru;
 
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.xml.xpath.XPathExpressionException;
@@ -22,11 +21,11 @@
  * Metadata for a patch, as defined by ARU.
  * Simple bean for holding metadata obtained from ARU for a given patch ID and version.
  */
-public class AruPatch implements Comparable<AruPatch> {
+public class AruPatch {
     private static final LoggingFacade logger = LoggingFactory.getLogger(AruPatch.class);
 
     private String patchId;
-    private Version version;
+    private String version;
     private String description;
     private String product;
     private String release;
@@ -51,15 +50,11 @@ public AruPatch patchId(String value) {
      * @return The string value of the version found in ARU.
      */
     public String version() {
-        if (version != null) {
-            return version.toString();
-        } else {
-            return null;
-        }
+        return version;
     }
 
     public AruPatch version(String value) {
-        version = new Version(value);
+        version = value;
         return this;
     }
 
@@ -294,27 +289,4 @@ private static AruPatch selectPatchOffline(List<AruPatch> patches, String provid
     public String toString() {
         return patchId + " - " + description;
     }
-
-    @Override
-    public int compareTo(AruPatch obj) {
-        return version.compareTo(obj.version);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        AruPatch aruPatch = (AruPatch) o;
-        return Objects.equals(patchId, aruPatch.patchId) && Objects.equals(version, aruPatch.version)
-            && Objects.equals(release, aruPatch.release);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(patchId, version, release);
-    }
 }
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/AruUtil.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/AruUtil.java
index e7aebdc80..519aac9e6 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/AruUtil.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/AruUtil.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2022, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.aru;
@@ -167,7 +167,7 @@ public List<AruPatch> getRecommendedPatches(FmwInstallerType type, String versio
         for (AruProduct product : type.products()) {
             List<AruPatch> patches = getRecommendedPatches(product, version, userId, password);
             // temporary, until OHS stops using same release number and product ID for two different installs
-            if (type == FmwInstallerType.OHS) {
+            if (type == FmwInstallerType.OHS_DB19) {
                 if (product == AruProduct.OHS) {
                     patches = patches.stream().filter(p -> p.description().contains(" DB19C "))
                         .collect(Collectors.toList());
@@ -267,7 +267,7 @@ public PatchLists(List<InstalledPatch> installedPatches, List<AruPatch> candidat
     }
 
     /**
-     * Validate patches conflicts by passing a list of patches.
+     * Check for patch conflicts by passing a list of patches.
      *
      * @param installedPatches opatch lsinventory content (null if none is passed)
      * @param patches          A list of patches number
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/Version.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/Version.java
deleted file mode 100644
index dfec386af..000000000
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/aru/Version.java
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2023, Oracle and/or its affiliates.
-// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
-
-package com.oracle.weblogic.imagetool.aru;
-
-import java.util.Arrays;
-
-public class Version implements Comparable<Version> {
-    private final int[] sequence;
-    private final String stringValue;
-
-    /**
-     * Representation of the ARU version number used for Oracle products.
-     * Version must be one or more integers separated by a period, ".".
-     * @param value String to be parsed as the ARU version.
-     */
-    public Version(String value) {
-        stringValue = value;
-
-        if (value != null && !value.isEmpty()) {
-            // split version into a sequence tokens using the period separator
-            sequence = Arrays.stream(value.split("\\."))
-                .mapToInt(Integer::parseInt)
-                .toArray();
-        } else {
-            sequence = new int[1];
-        }
-    }
-
-    /**
-     * Return the sequence of version tokens padded to the minimum length with 0's.
-     * The sequence will NOT be truncated.
-     * @param minLength minimum number of version tokens in the array.
-     * @return sequence of version tokens
-     */
-    public int[] getSequence(int minLength) {
-        if (sequence.length < minLength) {
-            return Arrays.copyOf(sequence, minLength);
-        }
-        return sequence;
-    }
-
-    /**
-     * Compare this version number against the provided version, returning -1, 0, or 1 if
-     * this version is less than, equal to, or greater than the provided version, respectively.
-     * @param provided the object to be compared.
-     * @return -1, 0, or 1 if this version is less than, equal to, or greater than the provided version
-     */
-    @Override
-    public int compareTo(Version provided) {
-        int match = 0;
-        int sequenceLength = Math.max(sequence.length, provided.sequence.length);
-        int[] mySequence = getSequence(sequenceLength);
-        int[] providedSequence = provided.getSequence(sequenceLength);
-
-        for (int i = 0; i < sequenceLength; i++) {
-            if (mySequence[i] > providedSequence[i]) {
-                match = 1;
-            } else if (mySequence[i] < providedSequence[i]) {
-                match = -1;
-            }
-            if (match != 0) {
-                break;
-            }
-        }
-
-        return match;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        Version version = (Version) o;
-        return this.compareTo(version) == 0;
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(sequence);
-    }
-
-    @Override
-    public String toString() {
-        return stringValue;
-    }
-}
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/builder/BuildCommand.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/builder/BuildCommand.java
index 98597cdcd..fa55d133e 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/builder/BuildCommand.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/builder/BuildCommand.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, 2021, Oracle and/or its affiliates.
+// Copyright (c) 2020, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.builder;
@@ -17,8 +17,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import com.oracle.weblogic.imagetool.logging.LoggingFacade;
 import com.oracle.weblogic.imagetool.logging.LoggingFactory;
@@ -28,10 +26,12 @@
 public class BuildCommand {
     private static final LoggingFacade logger = LoggingFactory.getLogger(BuildCommand.class);
 
+    private final String executable;
     private final List<String> command;
     private final List<BuildArg> buildArgs;
     private List<String> additionalOptions;
     private final String context;
+    private boolean useBuildx = false;
 
     /**
      * Create a build command for creating an image.  At some point, it might
@@ -41,7 +41,8 @@ public class BuildCommand {
     public BuildCommand(String buildEngine, String contextFolder) {
         Objects.requireNonNull(contextFolder);
         buildArgs = new ArrayList<>();
-        command = Stream.of(buildEngine, "build", "--no-cache").collect(Collectors.toList());
+        executable = buildEngine;
+        command = new ArrayList<>();
         context = contextFolder;
     }
 
@@ -59,6 +60,22 @@ public BuildCommand tag(String value) {
         return this;
     }
 
+    /**
+     * Add container build platform.  Pass the desired
+     * build architecture to the build process.
+     * @param value a single platform name.
+     * @return this
+     */
+    public BuildCommand platform(String value) {
+        if (Utils.isEmptyString(value)) {
+            return this;
+        }
+        command.add("--platform");
+        command.add(value);
+        useBuildx = true;
+        return this;
+    }
+
     /**
      * Always remove intermediate containers if set to true.
      * By default, Docker leaves intermediate containers when the build fails which is not ideal for CI/CD servers.
@@ -237,7 +254,13 @@ private CloseableList<PrintWriter> createPrintWriters(List<OutputStream> outputS
     }
 
     private List<String> getCommand(boolean showPasswords) {
-        List<String> result = new ArrayList<>(command);
+        List<String> result = new ArrayList<>();
+        result.add(executable);
+        if (useBuildx) {
+            result.add("buildx");
+        }
+        result.add("build");
+        result.addAll(command);
         if (additionalOptions != null && !additionalOptions.isEmpty()) {
             result.addAll(additionalOptions);
         }
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cachestore/OPatchFile.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cachestore/OPatchFile.java
index 30e93483d..58c2425fc 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cachestore/OPatchFile.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cachestore/OPatchFile.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, 2022, Oracle and/or its affiliates.
+// Copyright (c) 2020, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.cachestore;
@@ -110,8 +110,11 @@ private static AruPatch getAruPatchOnline(String patchNumber, String providedVer
                 throw new VersionNotFoundException(patchNumber, providedVersion, patches);
             }
         } else {
-            // Select the newest (highest numbered) patch
-            selectedPatch = patches.stream().max(Comparator.naturalOrder()).orElse(null);
+            // Compare the ARU OPatch patches using the patch version field, like 12.2.1.4.0
+            Comparator<AruPatch> patchVersionComparator =
+                Comparator.comparing(AruPatch::version, Utils::compareVersionsNullsFirst);
+            // Select the newest (highest version) OPatch install/patch
+            selectedPatch = patches.stream().max(patchVersionComparator).orElse(null);
         }
         return selectedPatch;
     }
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/AddInstallerEntry.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/AddInstallerEntry.java
index 69947194b..79c4533f4 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/AddInstallerEntry.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/AddInstallerEntry.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2021, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.cli.cache;
@@ -22,12 +22,27 @@ public CommandResponse call() throws CacheStoreException {
         if ("NONE".equalsIgnoreCase(version)) {
             throw new IllegalArgumentException("IMG-0105");
         }
-        String key = String.format("%s%s%s", type, CacheStore.CACHE_KEY_SEPARATOR, version);
-        return addToCache(key);
+
+        return addToCache();
+    }
+
+    @Override
+    public String getKey() {
+        StringBuilder key = new StringBuilder(25)
+            .append(type)
+            .append(CacheStore.CACHE_KEY_SEPARATOR)
+            .append(version);
+
+        if (architecture != null) {
+            key.append(CacheStore.CACHE_KEY_SEPARATOR)
+                .append(architecture);
+        }
+
+        return key.toString();
     }
 
     @Option(
-            names = {"--type"},
+            names = {"-t", "--type"},
             description = "Type of installer. Valid values: ${COMPLETION-CANDIDATES}",
             required = true,
             defaultValue = "wls"
@@ -35,10 +50,16 @@ public CommandResponse call() throws CacheStoreException {
     private InstallerType type;
 
     @Option(
-            names = {"--version"},
+            names = {"-v", "--version"},
             description = "Installer version. Ex: For WLS|FMW use 12.2.1.3.0 For jdk, use 8u201",
             required = true
     )
     private String version;
 
+    @Option(
+        names = {"-a", "--architecture"},
+        description = "(Optional) Installer architecture. Ex: linux/amd64 or linux/arm64."
+    )
+    private String architecture;
+
 }
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/AddPatchEntry.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/AddPatchEntry.java
index 30bba608b..ff882d4b3 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/AddPatchEntry.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/AddPatchEntry.java
@@ -17,12 +17,16 @@
 )
 public class AddPatchEntry extends CacheAddOperation {
 
+    public String getKey() {
+        return patchId;
+    }
+
     @Override
     public CommandResponse call() throws Exception {
         try {
             if (patchId != null && !patchId.isEmpty()) {
                 Utils.validatePatchIds(Collections.singletonList(patchId), true);
-                return addToCache(patchId);
+                return addToCache();
             } else {
                 return CommandResponse.error("IMG-0076", "--patchId");
             }
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/CacheAddOperation.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/CacheAddOperation.java
index 72ba91683..041de8a0d 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/CacheAddOperation.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/cache/CacheAddOperation.java
@@ -14,12 +14,15 @@
 
 public abstract class CacheAddOperation extends CacheOperation {
 
-    CommandResponse addToCache(String key) throws CacheStoreException {
+    public abstract String getKey();
+
+    CommandResponse addToCache() throws CacheStoreException {
         // if file is invalid or does not exist, return an error
         if (filePath == null || !Files.isRegularFile(filePath)) {
             return CommandResponse.error("IMG-0049", filePath);
         }
 
+        String key = getKey();
         // if the new value is the same as the existing cache value, do nothing
         String existingValue = cache().getValueFromCache(key);
         if (absolutePath().toString().equals(existingValue)) {
@@ -36,7 +39,7 @@ CommandResponse addToCache(String key) throws CacheStoreException {
         return CommandResponse.success("IMG-0050", key, cache().getValueFromCache(key));
     }
 
-    Path absolutePath() {
+    private Path absolutePath() {
         if (absolutePath == null) {
             absolutePath = filePath.toAbsolutePath();
         }
@@ -53,8 +56,8 @@ Path absolutePath() {
 
 
     @Option(
-        names = {"--path"},
-        description = "Location on disk. For ex: /path/to/patch-or-installer.zip",
+        names = {"-p", "--path"},
+        description = "Location of the file on disk. For ex: /path/to/patch-or-installer.zip",
         required = true
     )
     private Path filePath;
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/menu/CommonCreateOptions.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/menu/CommonCreateOptions.java
index 287bf0bbd..727c9ef0c 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/menu/CommonCreateOptions.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/menu/CommonCreateOptions.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, 2022, Oracle and/or its affiliates.
+// Copyright (c) 2021, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.cli.menu;
@@ -34,14 +34,14 @@ void prepareNewImage() throws IOException, InterruptedException, XPathExpression
         copyOptionsFromImage();
 
         if (dockerfileOptions.installJava()) {
-            CachedFile jdk = new CachedFile(InstallerType.JDK, jdkVersion);
+            CachedFile jdk = new CachedFile(InstallerType.JDK, jdkVersion, getBuildPlatform());
             Path installerPath = jdk.copyFile(cache(), buildDir());
             dockerfileOptions.setJavaInstaller(installerPath.getFileName().toString());
         }
 
         if (dockerfileOptions.installMiddleware()) {
             MiddlewareInstall install =
-                new MiddlewareInstall(getInstallerType(), installerVersion, installerResponseFiles);
+                new MiddlewareInstall(getInstallerType(), installerVersion, installerResponseFiles, getBuildPlatform());
             install.copyFiles(cache(), buildDir());
             dockerfileOptions.setMiddlewareInstall(install);
         } else {
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/menu/CommonOptions.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/menu/CommonOptions.java
index b80f0cfcc..baae90368 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/menu/CommonOptions.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/cli/menu/CommonOptions.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2022, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.cli.menu;
@@ -122,6 +122,7 @@ BuildCommand getInitialBuildCmd(String contextFolder) {
 
         cmdBuilder.forceRm(!skipcleanup)
             .tag(imageTag)
+            .platform(buildPlatform)
             .network(buildNetwork)
             .pull(buildPull)
             .additionalOptions(buildOptions)
@@ -312,6 +313,10 @@ public String buildId() {
         return buildId;
     }
 
+    public String getBuildPlatform() {
+        return buildPlatform;
+    }
+
     @Option(
         names = {"--tag"},
         paramLabel = "<image tag>",
@@ -365,12 +370,14 @@ public String buildId() {
 
     @Option(
         names = {"--additionalBuildCommands"},
+        paramLabel = "<filename>",
         description = "path to a file with additional build commands."
     )
     private Path additionalBuildCommandsPath;
 
     @Option(
         names = {"--additionalBuildFiles"},
+        paramLabel = "<filename>",
         split = ",",
         description = "comma separated list of files that should be copied to the build context folder."
     )
@@ -404,6 +411,7 @@ public String buildId() {
 
     @Option(
         names = {"--builder", "-b"},
+        paramLabel = "<executable name>",
         description = "Executable to process the Dockerfile."
             + " Use the full path of the executable if not on your path."
             + " Defaults to 'docker', or, when set, to the value in environment variable WLSIMG_BUILDER."
@@ -412,6 +420,7 @@ public String buildId() {
 
     @Option(
         names = {"--target"},
+        paramLabel = "<target environment>",
         description = "Apply settings appropriate to the target environment.  Default: ${DEFAULT-VALUE}."
             + "  Supported values: ${COMPLETION-CANDIDATES}."
     )
@@ -419,10 +428,18 @@ public String buildId() {
 
     @Option(
         names = {"--build-arg"},
+        paramLabel = "<arg=value>",
         description = "Additional argument passed directly to the build engine."
     )
     Map<String,String> buildArgs;
 
+    @Option(
+        names = {"--platform"},
+        paramLabel = "<target platform>",
+        description = "Set the target platform to build. Example: linux/amd64 or linux/arm64"
+    )
+    private String buildPlatform;
+
     @Parameters(
         description = "Container build options.",
         hidden = true
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/FmwInstallerType.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/FmwInstallerType.java
index 740212f45..745a31f21 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/FmwInstallerType.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/FmwInstallerType.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2023, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.installer;
@@ -82,6 +82,9 @@ public enum FmwInstallerType {
     OHS(Utils.toSet(AruProduct.OHS, AruProduct.OAM_WG, AruProduct.WLS, AruProduct.JDBC, AruProduct.FMWPLAT,
         AruProduct.OSS, AruProduct.FIT),
         InstallerType.OHS),
+    OHS_DB19(Utils.toSet(AruProduct.OHS, AruProduct.OAM_WG, AruProduct.WLS, AruProduct.JDBC, AruProduct.FMWPLAT,
+        AruProduct.OSS, AruProduct.FIT),
+        InstallerType.OHS, InstallerType.DB19),
     ODI(Collections.singleton(AruProduct.ODI),
         InstallerType.ODI)
     ;
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/InstallerType.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/InstallerType.java
index 8f6e2015a..d58f354d4 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/InstallerType.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/InstallerType.java
@@ -20,6 +20,7 @@ public enum InstallerType {
     IDM("idm"),
     OAM("oam"),
     OHS("ohs"),
+    DB19("db19"),
     OUD("oud"),
     OID("oid"),
     WCC("wcc"),
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstall.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstall.java
index 5518d569e..c9603e309 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstall.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstall.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2021, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.installer;
@@ -8,6 +8,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.zip.ZipEntry;
@@ -30,7 +31,7 @@ public class MiddlewareInstall {
      * Get the install metadata for a given middleware install type.
      * @param type the requested middleware install type
      */
-    public MiddlewareInstall(FmwInstallerType type, String version, List<Path> responseFiles)
+    public MiddlewareInstall(FmwInstallerType type, String version, List<Path> responseFiles, String buildPlatform)
         throws FileNotFoundException {
 
         logger.info("IMG-0039", type.installerListString(), version);
@@ -39,8 +40,11 @@ public MiddlewareInstall(FmwInstallerType type, String version, List<Path> respo
         for (InstallerType installer : type.installerList()) {
             MiddlewareInstallPackage pkg = new MiddlewareInstallPackage();
             pkg.type = installer;
-            pkg.installer = new CachedFile(installer, version);
+            pkg.installer = new CachedFile(installer, version, buildPlatform);
             pkg.responseFile = new DefaultResponseFile(installer, type);
+            if (installer.equals(InstallerType.DB19)) {
+                pkg.preinstallCommands = Collections.singletonList("34761383/changePerm.sh /u01/oracle");
+            }
             addInstaller(pkg);
         }
         setResponseFiles(responseFiles);
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstallPackage.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstallPackage.java
index e7bf9c808..2cb06550f 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstallPackage.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstallPackage.java
@@ -1,8 +1,10 @@
-// Copyright (c) 2020, 2021, Oracle and/or its affiliates.
+// Copyright (c) 2020, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.installer;
 
+import java.util.List;
+
 import com.oracle.weblogic.imagetool.api.model.CachedFile;
 
 public class MiddlewareInstallPackage {
@@ -11,6 +13,7 @@ public class MiddlewareInstallPackage {
     CachedFile installer;
     String installerFilename;
     String jarName;
+    List<String> preinstallCommands;
     boolean isZip = true;
     boolean isBin = false;
 }
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/BuildPlatform.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/BuildPlatform.java
new file mode 100644
index 000000000..46649afd6
--- /dev/null
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/BuildPlatform.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2024, Oracle and/or its affiliates.
+// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+package com.oracle.weblogic.imagetool.util;
+
+import com.oracle.weblogic.imagetool.logging.LoggingFacade;
+import com.oracle.weblogic.imagetool.logging.LoggingFactory;
+
+public class BuildPlatform {
+    private static final LoggingFacade logger = LoggingFactory.getLogger(BuildPlatform.class);
+
+    public static final String ARM64 = "linux/arm64";
+    public static final String AMD64 = "linux/amd64";
+
+    public static final String OS_ARCH;
+
+    static {
+        OS_ARCH = System.getProperty("os.arch");
+        logger.fine("Local machine architecture is {0}", OS_ARCH);
+    }
+
+    private BuildPlatform() {
+        // just static methods for now
+    }
+
+    /**
+     * Get the build platform name using System property "os.arch".
+     * @return name of the build platform, like linux/amd64.
+     */
+    public static String getPlatformName() {
+        return getPlatformName(OS_ARCH);
+    }
+
+    /**
+     * Get the build platform name using the provided architecture name.
+     * @return name of the build platform, like linux/amd64.
+     */
+    public static String getPlatformName(String architecture) {
+        logger.entering(architecture);
+        String result;
+        switch (architecture) {
+            case "arm64":
+            case "aarch64":
+                result = ARM64;
+                break;
+            case "amd64":
+            case "x86_64":
+                result = AMD64;
+                break;
+            default:
+                // this can occur when the JDK provides an unknown ID for the OS in the system property os.arch
+                logger.warning("Unsupported architecture type {0}, defaulting to amd64");
+                result = AMD64;
+        }
+        logger.exiting(result);
+        return result;
+    }
+}
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/HttpUtil.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/HttpUtil.java
index f12f71eea..ff9a5ca26 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/HttpUtil.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/HttpUtil.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2022, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.util;
@@ -182,18 +182,12 @@ private static HttpRequestRetryHandler retryHandler() {
                 // Do not retry if over max retries
                 return false;
             }
-            if (exception instanceof InterruptedIOException) {
-                // Timeout
-                return false;
-            }
-            if (exception instanceof UnknownHostException) {
-                // Unknown host
-                return false;
-            }
-            if (exception instanceof SSLException) {
-                // SSL handshake failed
+            if (exception instanceof InterruptedIOException   // Timeout
+                || exception instanceof UnknownHostException  // Unknown Host
+                || exception instanceof SSLException) {       // SSL handshake failed
                 return false;
             }
+
             HttpClientContext clientContext = HttpClientContext.adapt(context);
             HttpRequest request = clientContext.getRequest();
             // return true if it is okay to retry this request type
diff --git a/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/Utils.java b/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/Utils.java
index dd9f1da38..2c2e88a30 100644
--- a/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/Utils.java
+++ b/imagetool/src/main/java/com/oracle/weblogic/imagetool/util/Utils.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2021, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.util;
@@ -303,6 +303,27 @@ public static void deleteFilesRecursively(String pathDir) throws IOException {
         logger.exiting();
     }
 
+    /**
+     * Compares two version strings.  A null-friendly comparator that considers null to be less than non-null.
+     * Any qualifiers are treated as older than the same version without
+     * a qualifier.  If both versions have qualifiers and are otherwise equal, they are compared using
+     * String.compareTo() to determine the result.
+     *
+     * @param thisVersion  - first version
+     * @param otherVersion  - second version
+     * @return returns 0 if the versions are equal, greater than zero if thisVersion is newer,
+     *     and less than zero if thisVersion is older.
+     */
+    public static int compareVersionsNullsFirst(String thisVersion, String otherVersion) {
+        if (isEmptyString(thisVersion)) {
+            return (isEmptyString(otherVersion)) ? 0 : -1;
+        } else if (isEmptyString(otherVersion)) {
+            return 1;
+        } else {
+            return compareVersions(thisVersion, otherVersion);
+        }
+    }
+
     /**
      * Compares two version strings.  Any qualifiers are treated as older than the same version without
      * a qualifier.  If both versions have qualifiers and are otherwise equal, they are compared using
@@ -314,8 +335,6 @@ public static void deleteFilesRecursively(String pathDir) throws IOException {
      *     and less than zero if thisVersion is older.
      */
     public static int compareVersions(String thisVersion, String otherVersion) {
-        int result = 0;
-
         if (isEmptyString(thisVersion) || isEmptyString(otherVersion)) {
             throw new IllegalArgumentException("cannot compare null strings");
         }
@@ -330,24 +349,22 @@ public static int compareVersions(String thisVersion, String otherVersion) {
 
         int fieldsToCompare = Math.min(thisVersionElements.length, otherVersionElements.length);
 
+        int result = 0;
         int idx;
         for (idx = 0; idx < fieldsToCompare; idx++) {
             int thisVersionNumber = Integer.parseInt(thisVersionElements[idx]);
             int otherVersionNumber = Integer.parseInt(otherVersionElements[idx]);
 
-            if (thisVersionNumber > otherVersionNumber) {
-                result = 1;
-                break;
-            } else if (thisVersionNumber < otherVersionNumber) {
-                result = -1;
-                break;
+            result = Integer.compare(thisVersionNumber, otherVersionNumber);
+            if (result != 0) {
+                return result;
             }
         }
 
         // Version fields compared so far are equal so check to see if one version number
         // has more fields than the other.
         //
-        if (result == 0 && thisVersionElements.length != otherVersionElements.length) {
+        if (thisVersionElements.length != otherVersionElements.length) {
             if (thisVersionElements.length > otherVersionElements.length) {
                 result = 1;
             } else {
@@ -355,36 +372,37 @@ public static int compareVersions(String thisVersion, String otherVersion) {
             }
         }
 
+        if (result != 0) {
+            return result;
+        }
+
         // Finally, look to see if one or both versions have a qualifier if they are otherwise the same.
         //
-        if (result == 0) {
-            int useCase = 0;
-            if (thisVersion.indexOf('-') != -1) {
-                useCase += 1;
-            }
-            if (otherVersion.indexOf('-') != -1) {
-                useCase += 2;
-            }
-            switch (useCase) {
-                case 0:
-                    break;
+        int useCase = 0;
+        if (thisVersion.indexOf('-') != -1) {
+            useCase += 1;
+        }
+        if (otherVersion.indexOf('-') != -1) {
+            useCase += 2;
+        }
+        switch (useCase) {
+            case 1:
+                result = -1;
+                break;
 
-                case 1:
-                    result = -1;
-                    break;
+            case 2:
+                result = 1;
+                break;
 
-                case 2:
-                    result = 1;
-                    break;
+            case 3:
+                String thisQualifier = thisVersion.substring(thisVersion.indexOf('-'));
+                String otherQualifier = otherVersion.substring(otherVersion.indexOf('-'));
+                result = thisQualifier.compareTo(otherQualifier);
+                break;
 
-                case 3:
-                    String thisQualifier = thisVersion.substring(thisVersion.indexOf('-'));
-                    String otherQualifier = otherVersion.substring(otherVersion.indexOf('-'));
-                    result = thisQualifier.compareTo(otherQualifier);
-                    break;
-                default:
-                    break;
-            }
+            case 0:
+            default:
+                break;
         }
         return result;
     }
diff --git a/imagetool/src/main/resources/docker-files/install-middleware.mustache b/imagetool/src/main/resources/docker-files/install-middleware.mustache
index a53322a02..f472c60d3 100644
--- a/imagetool/src/main/resources/docker-files/install-middleware.mustache
+++ b/imagetool/src/main/resources/docker-files/install-middleware.mustache
@@ -1,4 +1,4 @@
-# Copyright (c) 2021, Oracle and/or its affiliates.
+# Copyright (c) 2021, 2024, Oracle and/or its affiliates.
 # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 #
 # Installing Middleware
@@ -36,17 +36,26 @@ USER {{userid}}
 
 RUN echo "INSTALLING MIDDLEWARE" \
 {{#installPackages}}
-    && echo "INSTALLING {{type}}" \
-    && {{#isZip}}unzip -q {{{tempDir}}}/{{installerFilename}} "*.[jb][ai][rn]" -d {{{tempDir}}} &&{{/isZip}} \
-    {{^isBin}}{{{java_home}}}/bin/java -Xmx1024m -jar {{{tempDir}}}/{{jarName}} -silent ORACLE_HOME={{{oracle_home}}} \
-    -responseFile {{{tempDir}}}/{{responseFile.name}} -invPtrLoc {{inv_loc}}/oraInst.loc -ignoreSysPrereqs -force -novalidation {{/isBin}} \
-    {{#isBin}}chmod +x {{{tempDir}}}/{{jarName}} && \
-    {{{tempDir}}}/{{jarName}} -force -ignoreSysPrereqs -silent -responseFile {{{tempDir}}}/{{responseFile.name}} \
-    -invPtrLoc {{inv_loc}}/oraInst.loc ORACLE_HOME={{{oracle_home}}} -jreLoc {{{java_home}}} {{/isBin}} \
+  && echo "INSTALLING {{type}}" \
+  # If installer is packaged in a ZIP, extract it before running it
+  {{#isZip}}&& unzip -q {{{tempDir}}}/{{installerFilename}} -d {{{tempDir}}}/{{{type}}} {{/isZip}} \
+  {{#preinstallCommands}}&& {{{tempDir}}}/{{{type}}}/{{{.}}} {{/preinstallCommands}} \
+  # IF the installer is a JAR file (not a .bin), run the silent install using Java
+  {{^isBin}}  && {{{java_home}}}/bin/java -Xmx1024m -jar {{{tempDir}}}/{{{type}}}/{{jarName}} \
+    -silent ORACLE_HOME={{{oracle_home}}} \
+    -responseFile {{{tempDir}}}/{{responseFile.name}} \
+    -invPtrLoc {{inv_loc}}/oraInst.loc \
+    -ignoreSysPrereqs -force -novalidation {{/isBin}}  \
+  # If the installer is a BIN, make sure it is executable and run the installer
+  {{#isBin}}  && chmod +x {{{tempDir}}}/{{{type}}}/{{jarName}} \
+  && {{{tempDir}}}/{{{type}}}/{{jarName}} \
+    -force -ignoreSysPrereqs -silent \
+    -responseFile {{{tempDir}}}/{{responseFile.name}} \
+    -invPtrLoc {{inv_loc}}/oraInst.loc ORACLE_HOME={{{oracle_home}}} -jreLoc {{{java_home}}} {{/isBin}}  \
 {{/installPackages}}
-&& test $? -eq 0 \
-&& chmod -R g+r {{{oracle_home}}} \
-|| (grep -vh "NOTIFICATION" /tmp/OraInstall*/install*.log && exit 1)
+  && test $? -eq 0 \
+  && chmod -R g+r {{{oracle_home}}} \
+  || (grep -vh "NOTIFICATION" /tmp/OraInstall*/install*.log && exit 1)
 
 {{> fmw-patching}}
 
diff --git a/imagetool/src/test/java/com/oracle/weblogic/imagetool/aru/VersionTest.java b/imagetool/src/test/java/com/oracle/weblogic/imagetool/aru/VersionTest.java
deleted file mode 100644
index f0c135c56..000000000
--- a/imagetool/src/test/java/com/oracle/weblogic/imagetool/aru/VersionTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) 2023, Oracle and/or its affiliates.
-// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
-
-package com.oracle.weblogic.imagetool.aru;
-
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-@Tag("unit")
-class VersionTest {
-    @Test
-    void sameVersionNumber() {
-        Version a = new Version("1.2.3");
-        Version b = new Version("1.2.3");
-        assertEquals(0, b.compareTo(a));
-        assertEquals(0, a.compareTo(b));
-    }
-
-    private void compareDifferingVersions(String firstVersion, String laterVersion) {
-        Version v1 = new Version(firstVersion);
-        Version v2 = new Version(laterVersion);
-        assertEquals(1, v2.compareTo(v1));
-        assertEquals(-1, v1.compareTo(v2));
-    }
-
-    @Test
-    void differentVersionNumbers() {
-        compareDifferingVersions("1.2.3", "1.2.4");
-    }
-
-    @Test
-    void differentVersionLengths() {
-        compareDifferingVersions("1.2.3", "1.2.3.1");
-    }
-
-    @Test
-    void integerComparison() {
-        compareDifferingVersions("13.9.4.2.9", "13.9.4.2.10");
-    }
-
-    @Test
-    void nullVersion() {
-        compareDifferingVersions(null, "1.2.3");
-    }
-
-    @Test
-    void nonNumericVersion() {
-        assertThrows(NumberFormatException.class, () -> new Version("1.A.4"));
-        assertThrows(NumberFormatException.class, () -> new Version("1.2.3-SNAP"));
-    }
-
-    @Test
-    void allowsNull() {
-        Version a = new Version(null);
-        Version b = new Version("1.2.3");
-        assertEquals(1, b.compareTo(a));
-        assertEquals(-1, a.compareTo(b));
-    }
-
-    @Test
-    void equalObjects() {
-        Version a = new Version("1.2.3");
-        Version b = new Version("1.2.3");
-        assertEquals(a, b);
-        assertEquals(b, a);
-        assertEquals(a, a);
-        assertNotEquals(a, null);
-    }
-
-    @Test
-    void hashcodeTest() {
-        Version a = new Version("1.2.3");
-        Version b = new Version("1.2.3");
-        Version c = new Version("1.2.4");
-        assertEquals(a.hashCode(), b.hashCode());
-        assertNotEquals(a.hashCode(), c.hashCode());
-    }
-}
diff --git a/imagetool/src/test/java/com/oracle/weblogic/imagetool/builder/BuilderTest.java b/imagetool/src/test/java/com/oracle/weblogic/imagetool/builder/BuilderTest.java
index 7d98fdcc8..bfd029ce7 100644
--- a/imagetool/src/test/java/com/oracle/weblogic/imagetool/builder/BuilderTest.java
+++ b/imagetool/src/test/java/com/oracle/weblogic/imagetool/builder/BuilderTest.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, Oracle and/or its affiliates.
+// Copyright (c) 2021, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.builder;
@@ -17,7 +17,7 @@ class BuilderTest {
     private static final String BUILD_ENGINE = "docker";
 
     private String expected(String options) {
-        return String.format("%s build --no-cache %s %s", BUILD_ENGINE, options, BUILD_CONTEXT);
+        return String.format("%s build %s %s", BUILD_ENGINE, options, BUILD_CONTEXT);
     }
 
     @Test
diff --git a/imagetool/src/test/java/com/oracle/weblogic/imagetool/cachestore/CachedFileTest.java b/imagetool/src/test/java/com/oracle/weblogic/imagetool/cachestore/CachedFileTest.java
index a44e904f5..be2b5239f 100644
--- a/imagetool/src/test/java/com/oracle/weblogic/imagetool/cachestore/CachedFileTest.java
+++ b/imagetool/src/test/java/com/oracle/weblogic/imagetool/cachestore/CachedFileTest.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, 2021, Oracle and/or its affiliates.
+// Copyright (c) 2020, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.cachestore;
@@ -17,6 +17,7 @@
 import com.oracle.weblogic.imagetool.installer.InstallerType;
 import com.oracle.weblogic.imagetool.logging.LoggingFacade;
 import com.oracle.weblogic.imagetool.logging.LoggingFactory;
+import com.oracle.weblogic.imagetool.util.BuildPlatform;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
@@ -25,6 +26,8 @@
 import static com.oracle.weblogic.imagetool.cachestore.OPatchFile.DEFAULT_BUG_NUM;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertLinesMatch;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 @Tag("unit")
@@ -33,54 +36,105 @@ class CachedFileTest {
     static Path cacheDir;
     static CacheStore cacheStore;
     static final List<String> fileContents = Arrays.asList("A", "B", "C");
-    static final String SOME_VERSION = "12.2.1.3.0";
+    static final String ver12213 = "12.2.1.3.0";
 
     @BeforeAll
     static void setup(@TempDir Path tempDir, @TempDir Path cacheDir) throws IOException {
+        Path path12213 = tempDir.resolve("installer.file.122130.jar");
+        Files.write(path12213, fileContents);
+        Path path12214 = tempDir.resolve("installer.file.12214.jar");
+        Files.write(path12214, fileContents);
+        Path path1411 = tempDir.resolve("installer.file.141100.jar");
+        Files.write(path1411, fileContents);
+
         CachedFileTest.cacheDir = cacheDir;
         cacheStore  = new CacheStoreTestImpl(cacheDir);
-        // build a fake cache with two installers
-        String key1 = "wls_" + SOME_VERSION;
-        Path path1 = tempDir.resolve("installer.file.122130.jar");
-        Files.write(path1, fileContents);
-        cacheStore.addToCache(key1, path1.toString());
-        cacheStore.addToCache("wls_12.2.1.4.0", "/dont/care");
+        // build a fake cache with several installers
+        cacheStore.addToCache("wls_" + ver12213, path12213.toString());
+        cacheStore.addToCache("wls_12.2.1.4.0_" + BuildPlatform.getPlatformName(), path12214.toString());
+        cacheStore.addToCache("wls_14.1.1.0.0_amd64", path1411.toString());
 
         // OPatch files
-        cacheStore.addToCache(DEFAULT_BUG_NUM + "_13.9.2.0.0", "/dont/care");
-        cacheStore.addToCache(DEFAULT_BUG_NUM + "_13.9.4.0.0", "/dont/care");
-        cacheStore.addToCache(DEFAULT_BUG_NUM + "_13.9.2.2.2", "/dont/care");
+        cacheStore.addToCache(DEFAULT_BUG_NUM + "_13.9.2.0.0", "/not/used");
+        cacheStore.addToCache(DEFAULT_BUG_NUM + "_13.9.4.0.0", "/not/used");
+        cacheStore.addToCache(DEFAULT_BUG_NUM + "_13.9.2.2.2", "/not/used");
     }
 
     @Test
     void versionString() {
-        CachedFile wlsInstallerFile = new CachedFile(InstallerType.WLS, SOME_VERSION);
-
-        assertEquals("wls_" + SOME_VERSION, wlsInstallerFile.getKey(),
-            "Cached file getKey failed for WLS installer");
+        CachedFile wlsInstallerFile = new CachedFile(InstallerType.WLS, "12.2.1.3.0");
 
-        assertEquals(SOME_VERSION, wlsInstallerFile.getVersion(), "CachedFile returned wrong version");
+        assertEquals("wls_12.2.1.3.0", wlsInstallerFile.getKey(),
+            "CachedFile getKey() did not construct the cache key correctly");
+        assertEquals("12.2.1.3.0", wlsInstallerFile.getVersion(),
+            "CachedFile should return the version from the constructor");
+    }
 
-        // if the file ID has the version separator, CachedFile should ignore the version passed, and use the version
-        // in the ID.
-        CachedFile cf = new CachedFile("something_versionString", SOME_VERSION);
+    @Test
+    void userProvidedPatchVersionAsId() {
+        // User provided a patch ID with the version string in the ID
+        CachedFile cf = new CachedFile("something_versionString", "12.2.1.2.0");
+        // if the patch ID has the version, CachedFile should ignore the installer version passed to the constructor,
+        // and use the version in the ID.
         assertEquals("something_versionString", cf.getKey(),
-            "Cached file getKey failed for version string");
+            "CachedFile getKey() failed when version string was provided by the user in the ID");
 
         // getVersion should always return the version that was provided in the constructor, not the one in the ID
-        assertEquals(SOME_VERSION, cf.getVersion(), "CachedFile returned wrong version");
+        assertEquals("12.2.1.2.0", cf.getVersion(), "CachedFile returned wrong version");
     }
 
     @Test
-    void resolveFile() throws Exception {
+    void resolveFileNotFound() throws Exception {
         // resolve should fail for a CachedFile that is not in the store
         CachedFile fakeFile = new CachedFile(InstallerType.WLS, "10.3.6.0.0");
         assertThrows(FileNotFoundException.class, () -> fakeFile.resolve(cacheStore));
+    }
+
+    @Test
+    void resolveFileFindsFile() throws IOException {
+        // Resolve a CachedFile stored in the cache (created in test setup above)
+        CachedFile wlsInstallerFile = new CachedFile(InstallerType.WLS, ver12213);
+        String expected = cacheStore.getValueFromCache("wls_" + ver12213);
+        assertEquals(expected, wlsInstallerFile.resolve(cacheStore), "CachedFile did not resolve file");
+    }
+
+    @Test
+    void resolveNoArchFile() throws IOException {
+        // Look for a cache entry where the user specified the architecture/platform amd64
+        CachedFile wlsNoArch = new CachedFile(InstallerType.WLS, ver12213, "amd64");
+
+        // verify the cache is setup as expected.
+        // wls_12.2.1.3.0 is in the cache, but wls_12.2.1.3.0_amd64 is NOT in the cache
+        assertNull(cacheStore.getValueFromCache("wls_12.2.1.3.0_amd64"));
+        String expected = cacheStore.getValueFromCache("wls_12.2.1.3.0");
+        assertNotNull(expected);
+
+        assertEquals(expected, wlsNoArch.resolve(cacheStore), "CachedFile returned wrong file");
+    }
+
+    @Test
+    void resolveWithArchitecture() throws IOException {
+        // Look for a cache entry where the user specified the architecture/platform amd64
+        CachedFile wlsArch = new CachedFile(InstallerType.WLS, "14.1.1.0.0", "amd64");
+
+        // verify the cache is setup as expected.  wls_14.1.1.0.0_amd64 is in the cache
+        String expected = cacheStore.getValueFromCache("wls_14.1.1.0.0_amd64");
+        assertNotNull(expected);
+
+        assertEquals(expected, wlsArch.resolve(cacheStore), "CachedFile failed to find specific architecture");
+    }
+
+    @Test
+    void resolveFallbackToLocalArch() throws IOException {
+        // Look for a cache entry where the user did not specify the architecture/platform
+        CachedFile wlsArch = new CachedFile(InstallerType.WLS, "12.2.1.4.0");
+
+        // verify the cache is setup as expected.  wls_14.1.1.0.0_amd64 is in the cache, but wls_14.1.1.0.0 is not
+        assertNull(cacheStore.getValueFromCache("wls_12.2.1.4.0"));
+        String expected = cacheStore.getValueFromCache("wls_12.2.1.4.0_" + BuildPlatform.getPlatformName());
+        assertNotNull(expected);
 
-        // CachedFile resolve should result in the same behavior has getting the path from the cache store
-        CachedFile wlsInstallerFile = new CachedFile(InstallerType.WLS, SOME_VERSION);
-        String expected = cacheStore.getValueFromCache("wls_" + SOME_VERSION);
-        assertEquals(expected, wlsInstallerFile.resolve(cacheStore), "resolve failed for CachedFile");
+        assertEquals(expected, wlsArch.resolve(cacheStore), "CachedFile failed to check local architecture");
     }
 
     @Test
@@ -89,7 +143,7 @@ void copyFile(@TempDir Path contextDir) throws Exception {
         Level oldLevel = logger.getLevel();
         logger.setLevel(Level.OFF);
         try {
-            CachedFile wlsInstallerFile = new CachedFile(InstallerType.WLS, SOME_VERSION);
+            CachedFile wlsInstallerFile = new CachedFile(InstallerType.WLS, ver12213);
             // copy the file from the cache store to the fake build context directory
             Path result = wlsInstallerFile.copyFile(cacheStore, contextDir.toString());
             // check to see if the file was copied correctly by examining the contents of the resulting file
diff --git a/imagetool/src/test/java/com/oracle/weblogic/imagetool/cachestore/PatchFileTest.java b/imagetool/src/test/java/com/oracle/weblogic/imagetool/cachestore/PatchFileTest.java
index 9bb60e4ad..41cdd0af1 100644
--- a/imagetool/src/test/java/com/oracle/weblogic/imagetool/cachestore/PatchFileTest.java
+++ b/imagetool/src/test/java/com/oracle/weblogic/imagetool/cachestore/PatchFileTest.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, 2022, Oracle and/or its affiliates.
+// Copyright (c) 2020, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.cachestore;
@@ -404,6 +404,7 @@ void opatchNoRecommendedTest() throws Exception {
         String patchId = "2818673x";
         OPatchFile patchFile = OPatchFile.getInstance(patchId, "x", "x", cacheStore);
 
+        assertEquals("13.9.4.2.5", patchFile.getVersion());
         String filePath = patchFile.resolve(cacheStore);
 
         assertNotNull(filePath, "Patch resolve() failed to get file path from XML");
diff --git a/imagetool/src/test/java/com/oracle/weblogic/imagetool/cli/cache/AddInstallerEntryTest.java b/imagetool/src/test/java/com/oracle/weblogic/imagetool/cli/cache/AddInstallerEntryTest.java
index d05816af0..ff9d21f2b 100644
--- a/imagetool/src/test/java/com/oracle/weblogic/imagetool/cli/cache/AddInstallerEntryTest.java
+++ b/imagetool/src/test/java/com/oracle/weblogic/imagetool/cli/cache/AddInstallerEntryTest.java
@@ -1,67 +1,69 @@
-// Copyright (c) 2019, 2021, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.cli.cache;
 
-import java.io.ByteArrayOutputStream;
-import java.io.PrintWriter;
-
-import com.oracle.weblogic.imagetool.api.model.CommandResponse;
-import com.oracle.weblogic.imagetool.cli.ImageTool;
 import com.oracle.weblogic.imagetool.installer.InstallerType;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
+import picocli.CommandLine;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @Tag("unit")
 class AddInstallerEntryTest {
-
-    private ByteArrayOutputStream byteArrayOutputStream = null;
-    private PrintWriter printStream = null;
-
-    @BeforeEach
-    void setup() {
-        byteArrayOutputStream = new ByteArrayOutputStream();
-        printStream = new PrintWriter(byteArrayOutputStream);
-    }
-
-    @AfterEach
-    void teardown() {
-        if (printStream != null) {
-            printStream.close();
-        }
-    }
-
     @Test
     void testMissingParameters() {
-        ImageTool.run(new AddInstallerEntry(), printStream, printStream);
-        assertTrue(new String(byteArrayOutputStream.toByteArray()).contains("Missing required options"));
+        final AddInstallerEntry addCommand = new AddInstallerEntry();
+        // Empty command line throws exception because the required parameters were not specified
+        assertThrows(CommandLine.MissingParameterException.class, () ->
+            new CommandLine(addCommand).parseArgs()
+        );
     }
 
     @Test
     void testWrongType() {
-        ImageTool.run(new AddInstallerEntry(), printStream, printStream,
-                "--type", "a2z", "--version", "some_value", "--path", "/path/to/a/file");
-        assertTrue(new String(byteArrayOutputStream.toByteArray()).contains(
-                "Invalid value for option '--type'"));
+        final AddInstallerEntry addCommand = new AddInstallerEntry();
+        // The value for --type must be one of the pre-defined types
+        CommandLine.ParameterException pe = assertThrows(CommandLine.ParameterException.class, () ->
+            new CommandLine(addCommand)
+                .parseArgs("--type", "a2z", "--version", "some_value", "--path", "/path/to/a/file")
+        );
+        assertTrue(pe.getMessage().contains("--type"));
+
+        // repeat same command but use a valid type.  No exception should be thrown.
+        new CommandLine(addCommand)
+            .parseArgs("--type", "WLS", "--version", "some_value", "--path", "/path/to/a/file");
     }
 
     @Test
     void testMissingVersion() {
-        ImageTool.run(new AddInstallerEntry(), printStream, printStream,
-                "--type", InstallerType.WLS.toString(), "--path", "/path/to/a/file");
-        assertTrue(new String(byteArrayOutputStream.toByteArray()).contains(
-                "Missing required option: '--version=<version>'"));
+        final AddInstallerEntry addCommand = new AddInstallerEntry();
+        // The value for --version must be specified
+        CommandLine.ParameterException pe = assertThrows(CommandLine.ParameterException.class, () ->
+            new CommandLine(addCommand)
+                .parseArgs("--type", InstallerType.WLS.toString(), "--path", "/path/to/a/file")
+        );
+        assertTrue(pe.getMessage().contains("--version"));
+    }
+
+    @Test
+    void testValidParameters() {
+        final AddInstallerEntry addCommand = new AddInstallerEntry();
+        // The cache key should be a string made up of the type and version seperated by an underscore
+        new CommandLine(addCommand)
+            .parseArgs("--type", "WLS", "--version", "12.2.1.4", "--path", "/path/to/a/file");
+        assertEquals("wls_12.2.1.4", addCommand.getKey());
     }
 
     @Test
-    void testInvalidParameters() {
-        CommandResponse response = ImageTool.run(new AddInstallerEntry(), printStream, printStream, "--type",
-                InstallerType.WLS.toString(), "--version", "", "--path", "/path/to/non/existent/file");
-        assertEquals(1, response.getStatus());
+    void testArchKey() {
+        final AddInstallerEntry addCommand = new AddInstallerEntry();
+        // The cache key should be a string made up of the type, version, and architecture seperated by an underscore
+        new CommandLine(addCommand)
+            .parseArgs("--type", "WLS", "--version", "12.2.1.4", "-a", "amd64", "--path", "/path/to/a/file");
+        assertEquals("wls_12.2.1.4_amd64", addCommand.getKey());
     }
 }
diff --git a/imagetool/src/test/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstallTest.java b/imagetool/src/test/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstallTest.java
new file mode 100644
index 000000000..e357ebfeb
--- /dev/null
+++ b/imagetool/src/test/java/com/oracle/weblogic/imagetool/installer/MiddlewareInstallTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2024, Oracle and/or its affiliates.
+// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
+
+package com.oracle.weblogic.imagetool.installer;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+
+import com.oracle.weblogic.imagetool.ResourceUtils;
+import com.oracle.weblogic.imagetool.cachestore.CacheStore;
+import com.oracle.weblogic.imagetool.cachestore.CacheStoreTestImpl;
+import com.oracle.weblogic.imagetool.logging.LoggingFacade;
+import com.oracle.weblogic.imagetool.logging.LoggingFactory;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Tag("unit")
+class MiddlewareInstallTest {
+    static Path cacheDir;
+    static CacheStore cacheStore;
+    private static final LoggingFacade commandLogger = LoggingFactory.getLogger(MiddlewareInstall.class);
+    private static Level oldLevel;
+
+    @BeforeAll
+    static void setup(@TempDir Path cacheDir) throws IOException {
+        oldLevel = commandLogger.getLevel();
+        commandLogger.setLevel(Level.WARNING);
+
+        MiddlewareInstallTest.cacheDir = cacheDir;
+        cacheStore  = new CacheStoreTestImpl(cacheDir);
+        cacheStore.addToCache("wls_12.2.1.4.0",
+            ResourceUtils.resourcePath("/dummyInstallers/test-installer.zip").toString());
+    }
+
+    @AfterAll
+    static void tearDown() {
+        commandLogger.setLevel(oldLevel);
+    }
+
+    @Test
+    void copyInstaller(@TempDir Path buildContextDir) throws IOException {
+        // Test a simple WLS install type, and copy the files to the build context folder
+        MiddlewareInstall install = new MiddlewareInstall(FmwInstallerType.WLS, "12.2.1.4.0", null, null);
+        install.copyFiles(cacheStore, buildContextDir.toString());
+        // 2 files should be copied from cache to build context folder
+        assertTrue(Files.isRegularFile(buildContextDir.resolve("test-installer.zip")));
+        assertTrue(Files.isRegularFile(buildContextDir.resolve("wls.rsp")), "Response file not found");
+        // JAR name from inside the zip should be correctly identified
+        List<MiddlewareInstallPackage> installers = install.getInstallers();
+        assertEquals(1, installers.size());
+
+        MiddlewareInstallPackage pkg = installers.get(0);
+        assertTrue(pkg.isZip);
+        assertEquals("the-installer.jar", pkg.jarName);
+        assertEquals("wls.rsp", pkg.responseFile.name());
+        assertInstanceOf(DefaultResponseFile.class, pkg.responseFile);
+    }
+
+    @Test
+    void customResponseFile(@TempDir Path buildContextDir) throws IOException {
+        List<Path> customResponse =
+            Collections.singletonList(ResourceUtils.resourcePath("/dummyInstallers/dummyResponse.txt"));
+        // Test a simple WLS install type, and copy the files to the build context folder
+        MiddlewareInstall install = new MiddlewareInstall(FmwInstallerType.WLS, "12.2.1.4.0", customResponse, null);
+        install.copyFiles(cacheStore, buildContextDir.toString());
+        // 2 files should be copied from cache to build context folder
+        assertTrue(Files.isRegularFile(buildContextDir.resolve("test-installer.zip")));
+        assertTrue(Files.isRegularFile(buildContextDir.resolve("dummyResponse.txt")), "Response file not found");
+        // JAR name from inside the zip should be correctly identified
+        List<MiddlewareInstallPackage> installers = install.getInstallers();
+        assertEquals(1, installers.size());
+
+        MiddlewareInstallPackage pkg = installers.get(0);
+        assertTrue(pkg.isZip);
+        assertEquals("the-installer.jar", pkg.jarName);
+        assertEquals("dummyResponse.txt", pkg.responseFile.name());
+        assertInstanceOf(ProvidedResponseFile.class, pkg.responseFile);
+    }
+}
diff --git a/imagetool/src/test/java/com/oracle/weblogic/imagetool/util/DockerfileBuilderTest.java b/imagetool/src/test/java/com/oracle/weblogic/imagetool/util/DockerfileBuilderTest.java
index 298c12139..64aae8c9e 100644
--- a/imagetool/src/test/java/com/oracle/weblogic/imagetool/util/DockerfileBuilderTest.java
+++ b/imagetool/src/test/java/com/oracle/weblogic/imagetool/util/DockerfileBuilderTest.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2021, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.util;
@@ -28,7 +28,7 @@ class DockerfileBuilderTest {
      */
     @Test
     void validateMustacheAliases() throws IOException {
-        MiddlewareInstall install = new MiddlewareInstall(FmwInstallerType.WLS, "12.2.1.3", null);
+        MiddlewareInstall install = new MiddlewareInstall(FmwInstallerType.WLS, "12.2.1.3", null, null);
 
         DockerfileOptions dockerfileOptions = new DockerfileOptions("123")
             .setPatchingEnabled()
diff --git a/imagetool/src/test/java/com/oracle/weblogic/imagetool/util/UtilsTest.java b/imagetool/src/test/java/com/oracle/weblogic/imagetool/util/UtilsTest.java
index 92a0c6be9..a869d28fe 100644
--- a/imagetool/src/test/java/com/oracle/weblogic/imagetool/util/UtilsTest.java
+++ b/imagetool/src/test/java/com/oracle/weblogic/imagetool/util/UtilsTest.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2019, 2022, Oracle and/or its affiliates.
+// Copyright (c) 2019, 2024, Oracle and/or its affiliates.
 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
 
 package com.oracle.weblogic.imagetool.util;
@@ -43,10 +43,117 @@ class UtilsTest {
     private SystemProperties overrideProperties;
 
     @Test
-    void compareVersions() {
-        assertEquals(0, Utils.compareVersions("12.2.1.3.0", "12.2.1.3.0"));
-        assertTrue(Utils.compareVersions("1.0", "1.1") < 0);
-        assertTrue(Utils.compareVersions("1.1", "1.0") > 0);
+    void firstGreaterThanLast() throws Exception {
+        String thisVersion = "12.2.1.3.0";
+        String thatVersion = "12.2.1.2.0";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) > 0,
+            "Expected this " + thisVersion + " to be newer than that " + thatVersion);
+
+        thisVersion = "0.7.4";
+        thatVersion = "0.7.3";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) > 0,
+            "Expected this " + thisVersion + " to be newer than that " + thatVersion);
+
+        thisVersion = "0.8";
+        thatVersion = "0.7.4";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) > 0,
+            "Expected this " + thisVersion + " to be newer than that " + thatVersion);
+
+        thisVersion = "1.2.3";
+        thatVersion = "1.2";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) > 0,
+            "Expected this " + thisVersion + " to be newer than that " + thatVersion);
+
+        thisVersion = "13.9.4.2.10";
+        thatVersion = "13.9.4.2.9";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) > 0,
+            "Expected this " + thisVersion + " to be newer than that " + thatVersion);
+
+        thisVersion = "1.2.3";
+        thatVersion = "1.2.3-SNAPSHOT";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) > 0,
+            "Expected this " + thisVersion + " to be newer than that " + thatVersion);
+
+        thisVersion = "1.2.3-BETA1";
+        thatVersion = "1.2.3-ALPHA3";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) > 0,
+            "Expected this " + thisVersion + " to be newer than that " + thatVersion);
+
+        thisVersion = "1.2.3-SNAPSHOT";
+        thatVersion = "1.2.2";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) > 0,
+            "Expected this " + thisVersion + " to be newer than that " + thatVersion);
+    }
+
+    @Test
+    void secondGreaterThanFirst() throws Exception {
+        String thisVersion = "12.2.1.3.0";
+        String thatVersion = "12.2.1.4.0";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) < 0,
+            "Expected that " + thatVersion + " to be newer than this " + thisVersion);
+
+        thisVersion = "0.7.3";
+        thatVersion = "0.7.4";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) < 0,
+            "Expected that " + thatVersion + " to be newer than this " + thisVersion);
+
+        thisVersion = "0.7.4";
+        thatVersion = "1";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) < 0,
+            "Expected that " + thatVersion + " to be newer than this " + thisVersion);
+
+        thisVersion = "1.2";
+        thatVersion = "1.2.3";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) < 0,
+            "Expected that " + thatVersion + " to be newer than this " + thisVersion);
+
+        thisVersion = "1.2.3-SNAPSHOT";
+        thatVersion = "1.2.3";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) < 0,
+            "Expected that " + thatVersion + " to be newer than this " + thisVersion);
+
+        thisVersion = "1.2.3-ALPHA4";
+        thatVersion = "1.2.3-ALPHA5";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) < 0,
+            "Expected that " + thatVersion + " to be newer than this " + thisVersion);
+
+        thisVersion = "1.2.1";
+        thatVersion = "1.2.2-SNAPSHOT";
+        assertTrue(Utils.compareVersions(thisVersion, thatVersion) < 0,
+            "Expected that " + thatVersion + " to be newer than this " + thisVersion);
+    }
+
+    @Test
+    void versionsShouldBeEqual() throws Exception {
+        String thisVersion = "12.2.1.3.0";
+        String thatVersion = "12.2.1.3.0";
+        assertEquals(0, Utils.compareVersions(thisVersion, thatVersion),
+            "Expected server " + thatVersion + " to be the same as client " + thisVersion);
+
+        thisVersion = "0.7.4";
+        thatVersion = "0.7.4";
+        assertEquals(0, Utils.compareVersions(thisVersion, thatVersion),
+            "Expected server " + thatVersion + " to be the same as client " + thisVersion);
+
+        thisVersion = "1";
+        thatVersion = "1";
+        assertEquals(0, Utils.compareVersions(thisVersion, thatVersion),
+            "Expected server " + thatVersion + " to be the same as client " + thisVersion);
+
+        thisVersion = "1.2.3-ALPHA1";
+        thatVersion = "1.2.3-ALPHA1";
+        assertEquals(0, Utils.compareVersions(thisVersion, thatVersion),
+            "Expected server " + thatVersion + " to be the same as client " + thisVersion);
+
+        thisVersion = "1.2.3.4.5.6.7.8.9";
+        thatVersion = "1.2.3.4.5.6.7.8.9";
+        assertEquals(0, Utils.compareVersions(thisVersion, thatVersion),
+            "Expected server " + thatVersion + " to be the same as client " + thisVersion);
+
+        thisVersion = "0.7.4-SNAPSHOT";
+        thatVersion = "0.7.4-SNAPSHOT";
+        assertEquals(0, Utils.compareVersions(thisVersion, thatVersion),
+            "Expected server " + thatVersion + " to be the same as client " + thisVersion);
     }
 
     @Test
diff --git a/imagetool/src/test/resources/dummyInstallers/dummyResponse.txt b/imagetool/src/test/resources/dummyInstallers/dummyResponse.txt
new file mode 100644
index 000000000..2995a4d0e
--- /dev/null
+++ b/imagetool/src/test/resources/dummyInstallers/dummyResponse.txt
@@ -0,0 +1 @@
+dummy
\ No newline at end of file
diff --git a/imagetool/src/test/resources/dummyInstallers/test-installer.zip b/imagetool/src/test/resources/dummyInstallers/test-installer.zip
new file mode 100644
index 000000000..f87e0b812
Binary files /dev/null and b/imagetool/src/test/resources/dummyInstallers/test-installer.zip differ
diff --git a/imagetool/src/test/resources/patches/patch-2818673x.xml b/imagetool/src/test/resources/patches/patch-2818673x.xml
index 22888e82c..695483759 100644
--- a/imagetool/src/test/resources/patches/patch-2818673x.xml
+++ b/imagetool/src/test/resources/patches/patch-2818673x.xml
@@ -1,10 +1,56 @@
 
-<!-- Copyright (c) 2020, 2021, Oracle and/or its affiliates. -->
+<!-- Copyright (c) 2020, 2024, Oracle and/or its affiliates. -->
 <!-- Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. -->
 
 
 <results md5_sum="8ae88f8847e373ca6cc44516717e9e07">
     <generated_date in_epoch_ms="1606849997000">2020-12-01 19:13:17</generated_date>
+    <patch has_prereqs="n" has_postreqs="n" is_system_patch="n">
+        <bug>
+            <number>28186730</number>
+            <abstract><![CDATA[OPATCH 13.9.4.2.6 FOR EM 13.4, FMW/WLS 12.2.1.3.0, 12.2.1.4.0 AND 14.1.1.0.0]]></abstract>
+        </bug>
+        <name>28186730</name>
+        <type>Patch</type>
+        <status>Available</status>
+        <access id="m">Open access</access>
+        <url>
+            <patch_readme host="https://updates.oracle.com"><![CDATA[/Orion/Services/download?type=readme&aru=23901536]]></patch_readme>
+            <patch_details><![CDATA[/download/28186730.html]]></patch_details>
+        </url>
+        <request_id>23901536</request_id>
+        <urm_components>
+            <qparts ctype_id="201">
+                <qpart cid="468882" version="Q12753"><![CDATA[Oracle Global Lifecycle Management OPatch]]></qpart>
+            </qparts>
+            <urm_releases ctype_id="5">
+                <urm_release cid="1007556" version="13.9.4.2.6"><![CDATA[OPatch]]></urm_release>
+            </urm_releases>
+        </urm_components>
+        <product id="31944" bugdb_id="12753"><![CDATA[Oracle Global Lifecycle Management OPatch]]></product>
+        <release id="600000000159823" platform_patch_not_required="Y" cc="Y"><![CDATA[OPatch 13.9.4.2.6]]></release>
+        <platform id="2000" bugdb_id="289"><![CDATA[Generic Platform]]></platform>
+        <language id="0" iso_code="EN"><![CDATA[American English]]></language>
+        <translations_available>No</translations_available>
+        <classification id="174">General</classification>
+        <patch_classification id="174">General</patch_classification>
+        <support_level id="G">General Support</support_level>
+        <entitlements>
+            <entitlement code="SW"/>
+        </entitlements>
+        <size>45855234</size>
+        <files>
+            <file>
+                <name>p28186730_139425_Generic.zip</name>
+                <size>45855234</size>
+                <download_url host="https://updates.oracle.com"><![CDATA[/Orion/Services/download/p28186730_139425_Generic.zip?aru=23901536&patch_file=p28186730_139426_Generic.zip]]></download_url>
+                <digest type="SHA-256">8BFC99E439DA1D1663F88D6859EC19E9926A473C96B95889ABE5F45C055E1BB3</digest>
+                <digest type="SHA-1">41E3536305E6A71776717D700FA41B1A1AB0A493</digest>
+            </file>
+        </files>
+        <updated_date in_epoch_ms="1604594811000">2020-11-05 16:46:51</updated_date>
+        <released_date in_epoch_ms="1604592571000">2020-11-05 16:09:31</released_date>
+    </patch>
     <patch has_prereqs="n" has_postreqs="n" is_system_patch="n">
         <bug>
             <number>28186730</number>
diff --git a/installer/pom.xml b/installer/pom.xml
index 4012f0c0f..731dda304 100644
--- a/installer/pom.xml
+++ b/installer/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <artifactId>imagetool-parent</artifactId>
         <groupId>com.oracle.weblogic.lifecycle.imagetool</groupId>
-        <version>1.12.2</version>
+        <version>1.13.0</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
diff --git a/pom.xml b/pom.xml
index 465a4ddfe..9252d767f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.oracle.weblogic.lifecycle.imagetool</groupId>
     <artifactId>imagetool-parent</artifactId>
-    <version>1.12.2</version>
+    <version>1.13.0</version>
     <packaging>pom</packaging>
 
     <name>WebLogic Image Tool</name>
@@ -67,7 +67,7 @@
             <dependency>
                 <groupId>info.picocli</groupId>
                 <artifactId>picocli</artifactId>
-                <version>4.7.4</version>
+                <version>4.7.5</version>
             </dependency>
             <dependency>
                 <groupId>org.junit.jupiter</groupId>
@@ -242,7 +242,7 @@
                         <dependency>
                             <groupId>com.puppycrawl.tools</groupId>
                             <artifactId>checkstyle</artifactId>
-                            <version>10.12.4</version>
+                            <version>10.12.7</version>
                         </dependency>
                     </dependencies>
                 </plugin>
@@ -261,7 +261,7 @@
                 <plugin>
                     <groupId>org.jacoco</groupId>
                     <artifactId>jacoco-maven-plugin</artifactId>
-                    <version>0.8.10</version>
+                    <version>0.8.11</version>
                     <executions>
                         <execution>
                             <id>prepare-agent</id>
@@ -281,7 +281,7 @@
                 <plugin>
                     <groupId>org.sonarsource.scanner.maven</groupId>
                     <artifactId>sonar-maven-plugin</artifactId>
-                    <version>3.9.1.2184</version>
+                    <version>3.10.0.2594</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
diff --git a/tests/pom.xml b/tests/pom.xml
index 22799ec7b..fa21946fb 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -12,7 +12,7 @@
     <parent>
         <artifactId>imagetool-parent</artifactId>
         <groupId>com.oracle.weblogic.lifecycle.imagetool</groupId>
-        <version>1.12.2</version>
+        <version>1.13.0</version>
         <relativePath>../pom.xml</relativePath>
     </parent>