openEMS on immutable Linux, one script and no rebuild
Immutable distros are arguably where desktop Linux is headed — an
image-based, atomically-updated base with rpm-ostree or similar standing
between you and a broken system. I run Bluefin
for exactly that reason. The tradeoff is that anything expecting a
traditional, writable /usr — which is to say, most instructions for
building scientific software from source — doesn’t work the way it’s
documented. openEMS is a good example: getting it
running means installing a long list of apt/dnf build dependencies and
compiling the C++ core, CSXCAD, and the Octave/Python bindings against them.
None of that happens on an immutable root.
The standard answer on Bluefin is distrobox — a
traditional, mutable container (Ubuntu, Fedora, whatever) that shares your
host $HOME and integrates tightly enough that GUI apps, GPU access, and
$PATH binaries feel local. That solves the “can I install packages”
problem. It doesn’t solve the “I don’t want to redo an afternoon of build
setup every time I spin up a fresh container” problem — and immutable-distro
workflows lean on disposable containers precisely because rebuilding them
from scratch is supposed to be cheap.
So I wrote a setup script that makes it cheap for openEMS specifically: openems-setup-distrobox. Point it at a fresh Ubuntu 24.04 distrobox and it builds the entire toolchain — openEMS/CSXCAD from source, Octave and Python bindings, JupyterLab, VS Code — in one run, with GUI tools working out of the box.
What it does
The script (openems_setup_distrobox.sh) is a single, idempotent-ish bash
script you run inside the container. It:
- Installs the full
aptbuild dependency list (Boost, CGAL, HDF5, VTK/Qt,bison/flex, etc.) and pulls inoctave/octave-dev. - Clones and builds openEMS + CSXCAD from source
(
--with-hyp2mat --with-CTB --python), installs to/usr/local, and registers the shared libraries withldconfig. - Creates a system Python virtualenv (managed with uv,
pinned to the distro’s Python 3.12) and builds the Python bindings into it,
so the
openEMS/CSXCADmodules match the exact library version just compiled — a detail that matters more than it sounds, since a Python binding built against a different openEMS build will import fine and then fail or misbehave at the C++ boundary. - Wires the Octave
.mpaths into the site-wideoctavercand precompiles the.octhelpers (h5readatt_octaveand friends) as root, so the first user to touch them doesn’t hit a permission error trying to compile on-demand into a root-owned install dir. - Registers two Jupyter kernels — “Python (openEMS)” and “Octave” — and installs VS Code with the Python/Jupyter extensions preinstalled, so notebook development against either language works immediately.
- Sets zsh + Oh My Zsh as the default shell.
Nothing touches the shared home
The detail that makes this actually pleasant to use on an immutable distro:
distrobox containers share your host $HOME by design, so anything the
script writes into ~ would leak into the host and follow you into every
other container. The script avoids that entirely — everything goes into
/usr/local, /opt, and /etc, including things that normally assume
they own your home directory: Oh My Zsh, zsh’s ZDOTDIR, VS Code’s
user-data and extensions, and Jupyter’s config/data/runtime, all redirected
via XDG_* and tool-specific env vars set in /etc/profile.d/openems.sh.
Blow the container away and your host home directory never knew it was
there.
Octave, Python, and the GUI, all from inside the container
Once the script finishes, both scripting front ends work from a shell in the container, against the modules it just built:
python -c "import openEMS, CSXCAD; print('python bindings OK')"
octave --eval "InitCSX; disp('octave OK')"
The part that surprises people coming from Docker: the GUI tools work too.
octave --gui and AppCSXCAD — the Qt/VTK viewer for checking simulation
geometry and mesh before you burn an hour on a solve — launch from inside
the container and display on the host, because distrobox integrates X11/
Wayland rather than isolating it. The script forces Qt onto the xcb
platform plugin specifically to avoid a noisy (harmless) QSocketNotifier
warning that the Qt5 Wayland plugin throws on a Wayland host. The result is
that geometry review doesn’t require pulling anything out of the container
or resorting to X11 forwarding tricks — it’s a native window that happens
to be running in Ubuntu userspace on top of a Fedora Silverblue-family base.
Try it
distrobox create --name openems --image ubuntu:24.04
distrobox enter openems
git clone https://gitlab.com/strike-research/openems-setup-distrobox.git
cd openems-setup-distrobox
./openems_setup_distrobox.sh
Full details on the layout, configuration variables, and troubleshooting notes are in the README. If you’re on Bluefin, Aurora, or any other image-based distro and have been putting off openEMS because of the build story, this is meant to close that gap in one command.