πŸš€ Introducing Scalapptainer: drive Linux containers from Scala 3, on any OS. No daemon, no root

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. :cow_face:

:star: Check it out on GitHub: https://github.com/DFiantWorks/Scalapptainer

Apache-2.0, built with Mill. Give it a try and let me know what you build with it!

2 Likes

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

I also had a bit of difficulty getting started but finally managed to get it running. See my journey here:

(tl;dr - instead of using limactl start default, you need to use limactl start template:apptainer)

On What OS?

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.

Thanks for reporting!

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.

Fixed in v0.4.0

Ubuntu 24.04

Thanks. Have you retried with v0.4.0?

$ 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.

1 Like

I guess it’s safe for me to
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
?

I can try that on another box later.

If you have sudo, please first try to see if installing apptainer through apt will fix this first.

With this cow

$ cat cow.scala 
//> using dep io.github.dfiantworks::scalapptainer:0.4.0

/* preparations done: 
https://apptainer.org/docs/admin/main/installation.html#install-ubuntu-packages

sudo add-apt-repository -y ppa:apptainer/ppa
sudo apt update
sudo apt install -y apptainer
sudo apt install -y apptainer-suid
*/

import scalapptainer.*

@main def cow(): Unit =
  Apptainer.pull("docker://ghcr.io/dfiantworks/scalapptainer-cowsay").runInteractive()

it worked with perl warnings after above preps :tada:
(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 |
                ||     ||