Ever wanted a reproducible Linux toolchain (a specific compiler, simulator, CLI, or even a GUI app) that behaves identically on every machine? Apptainer gives you exactly that: a whole environment as a single, immutable .sif file. The catch is it only runs on Linux and usually means wrestling with shell scripts.
Scalapptainer makes that catch disappear. One dependency, and the same Scala code runs on Linux, Windows (via WSL2), and macOS (via Lima). It detects your host and routes commands to the right backend. On first use it installs Apptainer for you in user mode, bundling the helper tools the installer needs so even a minimal distro just works.
The API is typed and fluent: pull/build hand you back an immutable image handle you configure with bind mounts, env vars, and X11 forwarding, then exec / run / shell. Yes, that includes GUI apps rendered right on your desktop.
Want a taste? No project, no build file, just a .scala file:
//> using dep io.github.dfiantworks::scalapptainer:0.2.1
import scalapptainer.*
@main def cow(): Unit =
Apptainer.pull("docker://ghcr.io/dfiantworks/scalapptainer-cowsay").runInteractive()
scala run cow.scala
A ~30 MB image, an ASCII cow, and a random fortune, running in seconds.
Sounds really cool!! Tried it like above. But got this:
$ scala run cow.scala
Compiling project (Scala 3.8.4, JVM (17))
Compiled project (Scala 3.8.4, JVM (17))
ERROR : Could not write info to setgroups: Permission denied
ERROR : Error while waiting event for user namespace mappings: no event received
Your diagnostic issues are addressed in 0.3.0. Itβs a system configuration prerequisite that cannot be avoided, but at least now it should be clearer how to resolve it.
Actually, that is wrong. Aside from installing lima which is an admin thing, the rest of the invocations are in user land. Will add this automation to Scalapptainer.
$ cat cow.scala
//> using dep io.github.dfiantworks::scalapptainer:0.4.0
import scalapptainer.*
@main def cow(): Unit =
Apptainer.pull("docker://ghcr.io/dfiantworks/scalapptainer-cowsay").runInteractive()
$ scala run cow.scala
Starting compilation server
Downloading 2 dependencies
Compiling project (Scala 3.8.4, JVM (21))dfiantworks/scalapptainer_3/0.4.0/scalapptainer_3-0.4.0.jar
Compiled project (Scala 3.8.4, JVM (21)) / s)
Exception in thread "main" scalapptainer.UserNamespaceException: The native-linux backend forbids the unprivileged user namespace Apptainer's rootless engine needs to run
containers: creating one (or writing its uid/gid mapping) is denied. Scalapptainer detected this up front,
before fetching and installing Apptainer into a backend where it could never run.
On Linux this usually means the host β or the container/VM Scalapptainer runs inside β restricts unprivileged
user namespaces. Depending on the system, one of:
- Ubuntu 23.10+/24.04 restrict them via AppArmor; allow them with:
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
- Older Debian/RHEL kernels may need them enabled:
sudo sysctl -w kernel.unprivileged_userns_clone=1
sudo sysctl -w user.max_user_namespaces=15000
- If you are running inside another container (Docker/Podman/CI), launch it so it can nest user namespaces
(e.g. do not drop CAP_SETUID/CAP_SETGID, avoid a `setgroups`-restricting seccomp profile), or run on a
real host/VM instead.
Heavily sandboxed environments (many CI runners, online playgrounds such as Scastie) block this with no
unprivileged workaround β there Apptainer needs a setuid-root install, which requires root.
at scalapptainer.UserNamespaceException$.atInstall(exceptions.scala:65)
at scalapptainer.ApptainerInstaller.install(ApptainerInstaller.scala:63)
at scalapptainer.ApptainerInstaller.resolve(ApptainerInstaller.scala:50)
at scalapptainer.ApptainerInstaller.ensure$$anonfun$1(ApptainerInstaller.scala:33)
at scala.Option.getOrElse(Option.scala:203)
at scalapptainer.ApptainerInstaller.ensure(ApptainerInstaller.scala:36)
at scalapptainer.Apptainer.exec(Apptainer.scala:37)
at scalapptainer.Apptainer.run(Apptainer.scala:60)
at scalapptainer.Apptainer.pull(Apptainer.scala:100)
at cow$package$.cow(cow.scala:5)
at cow.main(cow.scala:4)
Great! Thank you for checking. While it might not be helpful on this particular system (depending on admin policy), itβs great to have better diagnostics.
it worked with perl warnings after above preps
(not obvoius that I also needed to install apptainer-suid)
$ scala run cow.scala
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:/home/bjornr/.sdkman/candidates/scala/3.8.4/maven2/org/scala-lang/scala-library/3.8.4/scala-library-3.8.4.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LC_TIME = "sv_SE.UTF-8",
LC_MONETARY = "sv_SE.UTF-8",
LC_ADDRESS = "sv_SE.UTF-8",
LC_TELEPHONE = "sv_SE.UTF-8",
LC_NAME = "sv_SE.UTF-8",
LC_MEASUREMENT = "sv_SE.UTF-8",
LC_IDENTIFICATION = "sv_SE.UTF-8",
LC_NUMERIC = "sv_SE.UTF-8",
LC_PAPER = "sv_SE.UTF-8",
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
_________________________________________
/ Do you know, I think that Dr. Swift was \
| silly to laugh about Laputa. I believe |
| it is a mistake to make a mock of |
| people, just because they think. There |
| are ninety thousand people in this |
| world who do not think, for every one |
| who does, and these people hate the |
| thinkers like poison. Even if some |
| thinkers are fanciful, it is wrong to |
| make fun of them for it. Better to |
| think about cucumbers even, than not to |
| think at all. |
| |
\ -- T. H. White /
-----------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||