2026-06-27 · openEMS / distrobox / immutable-linux / Bluefin

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 apt build dependency list (Boost, CGAL, HDF5, VTK/Qt, bison/flex, etc.) and pulls in octave/octave-dev.
  • Clones and builds openEMS + CSXCAD from source (--with-hyp2mat --with-CTB --python), installs to /usr/local, and registers the shared libraries with ldconfig.
  • 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/CSXCAD modules 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 .m paths into the site-wide octaverc and precompiles the .oct helpers (h5readatt_octave and 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.

← All articles