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
mpirunneeds shared libraries, MPI, and a handful of environment variables — notgcc,cmake, orgit. - 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.
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.
Minimizing the runtime layer
Three techniques keep the final layer small and predictable:
- Copy only the install prefix. Use
COPY --from=builder /opt/solver /opt/solverrather than copying the whole builder filesystem. AvoidCOPY --from=builder / /. - Strip and prune. Remove
*.astatic archives,include/headers,doc/, and example cases from the copied tree. For OpenFOAM this alone can save several GB. - Use a slim base.
ubuntu:22.04is ~77 MB;debian:bookworm-slimis 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.
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 matchinglibopenmpior copy the vendor MPI tree.- Solver starts but exits immediately with a license error — the license client libraries or
ANSYSLI_SERVERS/LM_LICENSE_FILEenvironment 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 withdocker historyand prune*.a,include/, anddoc/. - 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.