Port 0 doesn't exist in my infrastructure: typed Scala for home cluster management
Dan Billings — 2026-06-07
I manage a fleet of machines — a Mac Mini, an Arch Linux workstation with dual GPUs, and a WSL2 box on Windows. Each one runs different inference services, different systemd units, different package managers. Deploying across them used to mean three separate shell scripts that drift apart.
Now I write one typed Scala program and the compiler tells me before I run anything whether the configuration is valid. Not "probably fine" — the compiler literally refuses to accept anything that isn't a well-formed deployment specification. I mean "type-safe" exactly: if the program type-checks, the infrastructure is structurally sound.
Yes, I mean type-safe
Most Infrastructure-as-Code tools are templating engines. You write YAML with Jinja variables, or Terraform HCL with string interpolation. The tool validates that the syntax is parseable and that field names exist. That's it. You can still write port: -1, retry_count: "banana", or point a systemd service at a model file that doesn't exist — and the error won't surface until runtime, on the machine you're trying to configure.
I wanted something stronger. I wanted the compiler to reject invalid infrastructure the same way it rejects a List[String] where an Int is expected.
The result is ansible-scala — not a new provisioning tool, but a typed domain language that compiles to Ansible playbooks. The language is Scala 3. The structure is Free Monads. The constraints are Iron refinement types. And the interpreter emits standard Ansible JSON that runs on any host.
Free Monads as declarative configuration
A Free Monad separates the definition of an operation from its execution. In ansible-scala, this means the DSL describes what you want to do, not how you do it on a specific OS.
The algebra looks like this:
enum PackageOp[A]:
case Install(pkgs: List[PackageTag]) extends PackageOp[Unit]
case Remove(pkgs: List[PackageTag]) extends PackageOp[Unit]
case Upgrade extends PackageOp[Unit]
enum FileOp[A]:
case EnsureDir(path: FilePath, mode: UnixMode) extends FileOp[Unit]
case WriteContent(content: String, dest: FilePath) extends FileOp[Unit]
case GitClone(repo: String, dest: FilePath, version: GitRef) extends FileOp[Unit]
enum ServiceOp[A]:
case Start(name: NonEmptyString, scope: ServiceScope) extends ServiceOp[Unit]
case Enable(name: NonEmptyString, scope: ServiceScope) extends ServiceOp[Unit]
case Restart(name: NonEmptyString, scope: ServiceScope) extends ServiceOp[Unit]
Each enum is one algebra — one domain concept. Package management, file operations, service lifecycle. They're all wrapped into a coproduct type (InfraOp) and lifted into a Free monad (InfraProgram[A] = Free[InfraOp, A]). Smart constructors let you write fluent programs:
val program: InfraProgram[Unit] = for
_ <- install(Nginx, Git)
_ <- ensureDir("/opt/app", UnixMode("0755"))
_ <- enableService("nginx", ServiceScope.System)
yield ()
This is declarative infrastructure. The for comprehension reads like a specification: "I need Nginx and Git installed, a directory exists with these permissions, and the service is enabled." No shell escaping, no && chains, no sudo guessing.
The Free monad means this program is just data — a tree of operations. It doesn't run anything. You can inspect it, serialize it, test it, or transform it before execution. The interpreter step converts the tree into Ansible tasks, and different interpreters handle different platforms:
- Arch Linux →
pacman+systemd - macOS →
brew+launchd - Ubuntu →
apt+systemd - Fedora →
dnf+systemd
Same program, different interpreters. The compiler guarantees that every algebra case has an implementation for every platform, or it won't compile.
Iron refinement types: constraining the model
The Free Monad gives you structure. Iron gives you constraints.
Iron is a refinement types library for Scala that lets you attach compile-time predicates to types. Instead of accepting any Int for a port number, you define:
type PortRange = GreaterEqual[1] & LessEqual[65535]
type Port = Int :| PortRange
Now Port is literally an Int that the compiler has verified is between 1 and 65535. You cannot construct a Port from 0 or 70000 without the compiler refusing. The same pattern applies across the entire configuration model:
FilePathmust be non-emptyUnixModemust match the pattern0NNN(e.g.,"0755","0644")SystemdUnitNamemust end in.service,.timer,.socket, etc.SshPublicKeymust match a valid key formatBucketNamemust be lowercase, 3-63 characters, valid S3 namingVersionConstraintmust be a valid semantic versionPortmust be 1-65535
Every configuration value in the system is a refined type. When you write LlamaServerService(...).withPort(8080), the compiler checks that 8080 satisfies the Port constraint. When you write ensureDir("/opt/app", "0755"), it checks that "0755" satisfies the UnixMode pattern. Invalid configurations don't reach runtime — they're rejected at the call site, with a compiler error that tells you exactly which constraint failed.
This is the "type-safe" part. The model of your infrastructure is enforced by the type system. You can't accidentally deploy a service on port 0, write to an empty path, or enable a service with a malformed unit name. The compiler is your validation layer, and it runs before you touch any machine.
Scala 3 enums: no booleans, no raw strings
Every configuration dimension uses a Scala 3 enum. Not a boolean, not a string constant, not an Int magic number. Enums:
enum ServiceScope derives Encoder, Decoder:
case System
case User
enum BecomeMethod derives Encoder, Decoder:
case Sudo
case Pfexec
case Doas
enum ConnectionType derives Encoder, Decoder:
case Smart
case Ssh
case Local
case Chroot
case Docker
When you call enableService("nginx", ServiceScope.System), the type system guarantees that scope is one of System or User. There's no third option. No true/false that you have to remember which way around. No string typo that surfaces as a cryptic Ansible error halfway through a deployment.
The derives clause automatically generates JSON encoders and decoders, so the enum values flow directly into the Ansible playbook JSON with the correct serialization.
LLMs make this maintainable
An elaborate type system like this would be impractical to maintain alone. The enum definitions, the refined types, the algebra cases, the interpreter implementations — it's hundreds of types and thousands of lines of constraints. But with LLM-assisted development, the overhead collapses.
The workflow is simple: describe what you need in natural language, and the LLM generates the typed code. "Add a module for installing YOLO11 models with ONNX runtime" produces a new algebra enum, the refined types for model paths and version constraints, the smart constructors, and the interpreter cases — all with the correct Scala 3 syntax and Iron constraints. The compiler then validates whether the LLM got it right. If the types don't line up, you iterate. If they do, the code is structurally sound by construction.
This is the force multiplier. The type system catches the LLM's mistakes at compile time. The LLM fills in the tedious type definitions that would be painful to write by hand. Together, they let you maintain an elaborate constraint system that would be cost-prohibitive in a purely manual workflow.
I don't hand-write every enum case or regex constraint anymore. I describe the domain, the LLM generates the types, and the compiler tells me what to fix. The result is infrastructure code that's more constrained and more correct than anything I could produce manually at this scale.
Safe prototyping across the fleet
The practical payoff is that I can prototype infrastructure changes safely. Want to try a new model on the 4090? I write a Scala program that describes the deployment:
val program: InfraProgram[Unit] = for
_ <- install(Cuda, NvidiaUtils)
_ <- gitClone("https://github.com/ggml-org/llama.cpp", "/home/dan/llama-build", GitRef.Commit("04eb4c4"))
_ <- enableService("gemma4-12b", ServiceScope.System)
yield ()
The compiler checks that every operation is well-typed. The program doesn't run until I explicitly execute it. I can test it locally by running a YAML interpreter that prints the generated Ansible playbook to stdout, verifying the output before it ever touches a real machine. When I'm satisfied, I run it against the target host over SSH.
This is how I rolled out Gemma 4 MTP across the fleet: write the typed program once, verify the generated Ansible, deploy to the 4090. When the 5090 arrived, I wrote a new program with the different model and parameters. Same language, same constraints, same confidence.
If a deployment fails, the failure is at the Ansible level — a network issue, a permission problem, a missing package. Not a typo in a port number, not a boolean that was backwards, not a string constant that was slightly misspelled. Those errors were eliminated before the program could even be compiled.
Why this is unique
I'm not aware of any other home cluster management system that uses a compiled domain language with refinement types. Terraform has its own type system, but it's designed for cloud provisioning and doesn't express the low-level systemd + package management patterns that bare-metal inference services need. Ansible is powerful but untyped — you get validation at runtime, not compile time. Shell scripts are just... shell scripts.
The combination of Scala 3's type system, Free Monads for platform abstraction, Iron for value-level constraints, and LLMs for maintenance velocity creates something that's more expressive than Ansible, safer than Terraform, and more practical than either for managing bare-metal GPU inference services across a heterogeneous fleet.
The compiler is your safety net. The Free Monad is your abstraction layer. The refinement types are your validation engine. And the LLM is your force multiplier for keeping it all maintainable.
What's in the repo
The ansible-scala project on GitHub contains the full DSL:
- Algebras:
PackageOp,FileOp,ServiceOp,ShellOp,CommandOp,NpmOp,BunOp,HomebrewOp,CloudStorageOp— each a typed enum of domain operations - Smart constructors:
install(),ensureDir(),enableService(),gitClone()— fluent builders that returnInfraProgram[Unit] - Refined types:
Port,UnixMode,FilePath,SystemdUnitName,SshPublicKey,BucketName, and dozens more — all Iron-constrained - Platform interpreters: Arch, macOS, Ubuntu, Fedora — each converting the DSL to native package managers and service managers
- Playbook examples:
MacMini.scala,danarch4090.scala,Rtx5090Setup.scala,ArchLaptop.scala— real deployments for real machines
Everything is public. The playbooks show exactly how I configure each machine. The type definitions show the constraints I enforce. The interpreter implementations show how the same DSL produces different commands for different platforms.
This post explains the architecture behind the infrastructure code referenced in Qwen 3.6 MTP, RTX 5090 + NVFP4 + MTP, Gemma 4 MTP, vllm, Executable documentation, and YOLO11 pose estimation. All of those posts reference ansible-scala playbooks — this post explains what those playbooks actually are.