4. Building CAE solver images

CAE solver images are not ordinary application containers. A CFD or structural solver image typically bundles a licensed binary distribution, a Fortran/C++ runtime, MPI, math libraries such as BLAS/LAPACK or MKL, and a set of environment variables that the solver reads at startup. If you build these images naively — installing compilers, headers, and package managers into the same layer that runs the solver — you end up with 8–15 GB images, slow pulls across compute nodes, and a larger attack surface. Multi-stage builds solve this by separating the build environment from the runtime environment, copying only the artifacts the solver actually needs at run time.

Why multi-stage matters for solver workloads

The core idea is simple: a stage is a temporary filesystem. You can compile, extract, patch, and configure in one stage, then copy selected paths into a clean final stage. For CAE solvers this is especially valuable because:

  • Licensing and vendor binaries are large. OpenFOAM source builds pull in hundreds of MB of object files; Ansys and Abaqus ship precompiled trees with documentation and GUI components you may not need on a batch node.
  • Runtime dependencies are narrow. A solver launched with mpirun needs shared libraries, MPI, and a handful of environment variables — not gcc, cmake, or git.
  • Reproducibility. Pinning the build stage to a specific base image and copying deterministic paths makes the runtime layer auditable.

The general pattern is: builder stage → install toolchain and compile/extract → runtime stage → copy only the install prefix, set ENV, define ENTRYPOINT. The runtime stage should be based on a slim or distroless image whenever the solver's shared-library dependencies allow it.

Rule of thumb: if a file is not read by the solver process at run time, it does not belong in the final stage. Use ldd on the solver binary to enumerate the shared libraries that must be present.

OpenFOAM: compiling in builder, running slim

OpenFOAM is the most container-friendly of the four because it is open source and can be built from source. A typical multi-stage Dockerfile compiles OpenFOAM in a builder stage based on a full development image, then copies /opt/openfoam and the MPI installation into a slim runtime stage.

# ---- builder ----
FROM ubuntu:22.04 AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential cmake git ca-certificates \
    libopenmpi-dev openmpi-bin libptscotch-dev libfftw3-dev \
    && rm -rf /var/lib/apt/lists/*
WORKDIR /src
RUN git clone --depth 1 --branch v2312 https://github.com/OpenFOAM/OpenFOAM-11.git
RUN source OpenFOAM-11/etc/bashrc && ./Allwmake -j"$(nproc)"

# ---- runtime ----
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y --no-install-recommends \
    openmpi-bin libptscotch-7.0 libfftw3-double3 \
    && rm -rf /var/lib/apt/lists/*
COPY --from=builder /src/OpenFOAM-11 /opt/openfoam
ENV FOAM_INST_DIR=/opt/openfoam
ENV PATH=/opt/openfoam/bin:/opt/openfoam/platforms/linux64GccDPInt32Opt/bin:$PATH
ENV LD_LIBRARY_PATH=/opt/openfoam/platforms/linux64GccDPInt32Opt/lib:$LD_LIBRARY_PATH
WORKDIR /case
ENTRYPOINT ["/opt/openfoam/bin/simpleFoam"]

Note the deliberate choice to install only the runtime shared libraries (libptscotch-7.0, libfftw3-double3) in the final stage, not the -dev packages. The LD_LIBRARY_PATH must point at the compiled platform directory, which is where OpenFOAM places its solver binaries and shared objects.

Ansys Fluent, Abaqus, and LS-DYNA: extracting from vendor media

Commercial solvers cannot be compiled from source. The multi-stage pattern still applies, but the builder stage performs extraction and pruning rather than compilation. You mount or copy the vendor installation media, run the silent installer, then copy only the solver subtree into the runtime stage.

For Fluent, the installer produces a tree under /ansys_inc/v241/fluent. The runtime stage needs fluent, fluent_arch, the MPI bundled with Ansys (speos or intelmpi), and the license client libraries. GUI, documentation, and the CAD readers can be omitted on a batch node. For Abaqus, the essential runtime is /SIMULIA/Abaqus/Commands plus the solver executables under /SIMULIA/Abaqus/exec; the CAE GUI and documentation are separate. LS-DYNA is usually delivered as a single ls-dyna_smp_* or ls-dyna_mpp_* binary plus an MPI runtime, so the builder stage is mostly about selecting the correct binary and its matching MPI.

Licensing caution: vendor license terms may restrict copying installation trees into images. Confirm that your license agreement permits containerized redistribution within your organization before baking solver binaries into a shared registry image. In many deployments the vendor media is mounted read-only at run time instead.

Minimizing the runtime layer

Three techniques keep the final layer small and predictable:

  1. Copy only the install prefix. Use COPY --from=builder /opt/solver /opt/solver rather than copying the whole builder filesystem. Avoid COPY --from=builder / /.
  2. Strip and prune. Remove *.a static archives, include/ headers, doc/, and example cases from the copied tree. For OpenFOAM this alone can save several GB.
  3. Use a slim base. ubuntu:22.04 is ~77 MB; debian:bookworm-slim is smaller. Distroless images work only if the solver's dynamic linker and MPI are compatible.

After building, verify the image with docker run --rm image ldd /opt/solver/bin/solver to confirm no library is missing, and docker history image to inspect layer sizes. A well-built OpenFOAM runtime image lands around 1.5–2.5 GB; commercial solver images are typically 4–8 GB depending on which components are retained.

Multi-Stage Build: Builder Stage → Minimal Runtime Stage Builder Stage (temporary) Base: ubuntu:22.04 (full) build-essential, cmake, git, lib*-dev Compile / Extract Allwmake · silent vendor installer Install prefix produced /opt/openfoam · /ansys_inc · /SIMULIA Discarded at end of stage compilers, headers, .a archives, docs Not copied to runtime GUI, CAD readers, examples COPY --from Runtime Stage (final image) Base: ubuntu:22.04 (slim) only runtime shared libs Solver install prefix binaries + .so + MPI runtime ENV: PATH, LD_LIBRARY_PATH FOAM_INST_DIR · ANSYSLI_SERVERS ENTRYPOINT / WORKDIR simpleFoam · fluent 3ddp · abaqus Result: 1.5–8 GB image pull once, run on every node Verify: docker run --rm image ldd /opt/solver/bin/solver · docker history image · docker images

Practical verification and common failures

After the image builds, run a smoke test that exercises the solver without a full case. For OpenFOAM, docker run --rm image -help should print the solver banner. For Fluent, docker run --rm image fluent 3ddp -help confirms the launcher resolves. For Abaqus, docker run --rm image abaqus information=release prints the version and license configuration.

Typical failure symptoms and their causes:

  • error while loading shared libraries: libmpi.so.40 — the runtime stage is missing the MPI shared library. Install the matching libopenmpi or copy the vendor MPI tree.
  • Solver starts but exits immediately with a license error — the license client libraries or ANSYSLI_SERVERS / LM_LICENSE_FILE environment variable are absent. These must be set in the runtime stage, not the builder.
  • Image is unexpectedly large — a COPY --from=builder / / or an unstripped install tree. Inspect with docker history and prune *.a, include/, and doc/.
  • MPI ranks fail to connect — the runtime MPI version differs from the one the solver was built against. Pin both stages to the same MPI family and version.

The payoff of this discipline is a runtime image that pulls quickly to every compute node, starts the solver with a single mpirun command, and contains nothing that is not strictly required to execute the case.