Blog
Conflict Resolution Made Easy with Package Groups in Flox
Steve Swoyer | 19 August 2024
I love that I can use Flox to install legacy or bleeding-edge versions of software packages and core system libraries, even if I can’t satisfy their dependencies on my local system.
This is a lifesaver when I’m building software for other platforms. Or when I want to install cool new tools (or toys) that conflict with stuff on my system. Or when I need to build fit-for-purpose environments that make Very Hard things possible—and take them with me wherever I go.
The catch is that Flox also needs to manage its own package-level dependencies. Sometimes, to avoid certain kinds of dependency conflicts, it will refuse to install a specific version of a package.
The solution to this problem is to isolate fussy or finicky packages in distinct Package Groups. This blog explores how Flox Package Groups work, showing you how you can use them to manage different kinds of package-level conflicts in your Flox environments.
Example #1: Creating a traveling K8s toolkit
Let’s say I’m going on vacation and I want to keep noodling on a Golang-based microservice I’ve been refactoring. I can’t take my org’s production K8s cluster with me, but I can bring along a close facsimile—a K8s cluster on my laptop—thanks to Kubernetes in Docker (kind
) and Flox.
In fact, I can build almost everything I need for this use case into a Flox environment that runs anywhere. Thus giving me a portable toolkit I can take with me on my (wait for it) vk8tn!
First, let’s get the yucky stuff out of the way. I’m gonna need basic tools for managing my local K8s cluster—kubectl
for sure. Like most organizations, mine is still running an older version of K8s: 1.29.2, from almost a year ago. I’ll need a kubectl
client that’s as close to this as possible, so I can feel confident it’ll be compatible with my org’s prod
instance. Thankfully, Flox Catalog makes this trivially easy. Let’s just go ahead and flox install
exactly the kubectl
client I need:
But I’m not quite done! I might as well install a few other cool K8s tools, starting with essential stuff like helm
, but also super-useful utilities like stern
, or the incomparable k9s
. Just like before, I’ve got to grab a version of stern
that corresponds to my specific version of kubectl
, as well as specific versions of helm
and k9s
that likewise follow K8s versioning best practices. After I install all three I’ll—
In Greek tragedy, that abrupt cutting-off-in-mid-sentence is what’s known as “aposiopesis.”
It was the equivalent of the record-scratching sound you hear in movies or TV series—you know, when something totally unexpected happens. “Constraints are too tight?” What does this mean?
Flox Package Groups explained
Basically, Flox is telling us it can’t resolve how to install all of the specified package versions into the same environment, because at least one has dependencies that conflict with one or more of the others. So does this mean—to paraphrase a well-known apt
error message—Flox is “unable to correct problems and I have held broken packages?”
No! I can use Flox Package Groups to deal with issues like this.
The logic behind Package Groups is simple enough. Basically, we want the tools we use together to be from the same era of software. That way we know they’ll work together.
To give you an extreme example, building/running a binary that needs to link against a version of glibc
from 2018 (say, v2.28) is very different from building/running against glibc
v2.40, which was released just last month. The challenge is to use sets of packages that both share libraries and are ABI-compatible because they’re derived from the same era. Flox does this work for you, automatically figuring out which packages are compatible with which. Under its covers, Flox is partly powered by Nix—the proven open source package manager—and Nixpkgs is the upstream for Flox Catalog.
Creating Package Groups in a Flox environment
Flox enables a completely declarative approach to installing, versioning, and managing software. The Flox CLI declares packages for us in manifest.toml
, the software manifest and configuration artifact that lives inside every Flox environment. So putting kubectl
into its own Package Group and reinstalling the other packages I need should be sufficient to fix this problem.
All I’ve got to do is type flox edit
and I can easily start modifying the [install]
section of my manifest.toml
. Right now, it looks like this:
Putting kubectl
into its own Package Group is easy enough:
When I use flox edit
to make changes, Flox rebuilds and validates my environment in real time:
If everything checks out, I get the following message:
Okay. Isolating kubectl
in its own Package Group should do the trick. Now let’s reinstall the oth—
There’s that error again! When you think about it, however, this makes sense. If I could use just any versions of helm
, k9s
, or stern
in my environment, Flox could easily find releases that coexist with one another. But if I need specific versions of tools or libraries, there's a greater likelihood of dependency conflicts.
Why? Because each version is a snapshot of a point in time in that tool or library’s development. Let me say a little bit about that.
Software versioning made simple
Think of all the tools in a Flox environment as analogous to an exposure in photography: the longer the duration of the exposure, the more information (change) it captures. Environments containing a large number of tools, each pinned to specific versions, are like long exposures lasting for hours: both encompass so large a slice of time that dependency conflicts (or blurring movements) become inevitable.
In cases like this, it makes sense to separately install each tool and see which ones trigger a “constraints are too tight” error. Another viable option is just to create separate Package Groups for each one.
Off-screen, I did just that, zeroing in on k9s
, which I put into its own Package Group, called k9s
. But the latest version of Helm also refused to install unless I put it, too, into a separate Package Group. So this is what I ended up with:
I now have a declarative software manifest that I can amend as my requirements change. I can easily resolve dependency conflicts by isolating fussy packages into discrete package groups. And I can define as many different package groups as I need, which means I can create rich environments that include tools, libraries, frameworks, etc. that would otherwise refuse to coexist in a conventional system environment, or would need to be composed as part of a complicated multi-container runtime.
With that, I’m ready to take my K8s toolkit with me on my vk8tn.
All I need is a build environment to go along with it.
Example #2: A portable Golang build environment for K8s
Before signing off for vacation, I’d been refactoring my org’s API gateway service, using Go v1.22.5. I can easily get this version of Go from Flox Catalog, but—based on prior experience—I gotta wonder: is Go v1.22.5 gonna play nicely in my environment? Let's flox install
and find out:
The good news is that I now know there’s an easy fix for this: I can just put go
into its own Package Group. In fact, it's a good practice to always put related software build tools into their own Package Group, separating them from tools you use to bootstrap, manage, or provide core services for your environment.
For example, I also need to install ko
(a.k.a. “KOntainerize”), a neat tool I rely on to build and deploy Go stuff on K8s. I'll put this into a separate Package Group, build
, too. Here’s the snippet from my manifest.toml
.
The irony is that ko
isn’t at all fussy and doesn’t need to be isolated in a distinct Package Group. But it does make sense to keep related build tools together, not least because they're usually version-compatible and (for this reason) tend to share the same libraries and ABIs.
Speaking of which, I need to build other Go stuff into my Flox environment, like golangci-lint
, golangci-lint-langserver
, and delve
. Spoiler alert: I won’t need package groups for any of them either, but I’ll put them all into my build
Package Group, too. Once I do that, I should be good to go.
But why do I feel like I’m forgetting something? I got my vk8tn
management toolkit. I got my Go build environment. What am I forgetting? Oh. Yeah. K8s. I need a K8s cluster I can take with me.
I need K8s a go-go. Or something like it.
Up, running, and building with K8s, Go, and Flox
We need a Kubernetes cluster.
The Flox Catalog has the latest version of kind
, and (good news!) I don’t need to create a Package Group to install it in my environment. It also has ko
, which I’ve already said isn’t fussy about dependencies. Between them, I can use kubectl
, ko
, and kind
to automate just about everything for my vk8tn
environment and workflow. This gives me a way to define a local replica of my prod K8s cluster, build and compile my Go code, and deploy it as a containerized service to kind
/K8s. Here’s what the final [install]
section of my environment’s manifest.toml
looks like:
I added lazydocker
, a useful container management tool coded in Go, because I’m using kind
. The rest of my environment consists of basic K8s management stuff and Go/software build tools.
To put my environment through its paces, I’d first initialize my Go project as usual, putting the code for my API gateway service in my module’s project directory. Next, I’d spin up my kind
“cluster”:
Then I’d use kubectl
to generate a deployment.yaml
for running my service on kind
/K8s. From there, I’d set up my environment for ko
and kind
, exporting the names of my Docker repo and K8s cluster. Finally, I’d hand off to ko
, which compiles my app, builds my container, and pushes it to kind
/K8s. (I created a bash script to do all of this for me. See Housekeeping Notes, below.)
That’s basically it. Now let’s light this candle:
Deployment worked as expected! Let’s use kubectl
to check on the status of my app and pods:
Everything looks good. That’s what I like to see!
I’ve built stern
into my environment, and if I were having problems with my pods—which I did, offscreen, repeatedly, while putting together this walk-through!—it’d give me an invaluable debugging tool. Instead I’ll use k9s
(one of the cooler terminal tools I’ve used this year!) to give me an at-a-glance overview of the state and health of my K8s environment and workloads.
Success! My Golang API gateway service is up and running on my local K8s cluster.
I ping @here
in Slack about my new experiment. My teammates can reproduce the exact versions of each tool in my package groups:
Now we can work on refactoring it, iteratively building, testing, and deploying it.
You know, on the off chance I’m bored. While I’m on vacation.
Housekeeping Notes
My kind.yaml
is a basic config file that defines a control-plane node and two worker nodes, each of which uses the same kindest/node:v1.29.2
image. (This last is a kind
-specific Docker image bundling everything you need to run a Kubernetes node in a kind
cluster.) This way, I can spin up as many nodes as my org’s production cluster has, each running the same containerized version of Kubernetes.
The bash logic I use to compile, containerize, and deploy my Go API gateway service is below.
There isn’t much to it.
If I wanted to, I could paste this as a function into the [profile]
section of my environment’s manifest.toml
and create an alias (say, containerize
?) for it. That way, it would be available as a default “command” in my environment. Then I wouldn’t have to rely on shell scripts.
A Radical Approach to Building Software...That Isn’t Entirely New
Flox enables a radical approach to portable, reproducible software builds—but it isn’t entirely new.
Under its covers, Flox uses Nix-like concepts and methods to achieve portability, reproducibility, and platform-optimized performance. With Nixpkgs as its upstream, Flox offers more than 1 million software package and version combinations. When you flox install
software on MacOS, you get ARM or x86-64 packages compiled, built, and optimized for MacOS. The same is true on ARM and x86-64 Linux systems.
What is new and different about Flox is that it gives you the power of Nix without the steep learning curve. It ships as a single installable and has just a few commands. With five minutes or fewer of prep time, you can use Flox to start building software. Curious? Intrigued? Skeptical? Put Flox to the test today! You can start by downloading Flox here!
Note: Thanks to everyone at Flox, but, especially, to Leigh Capilli, Ross Turk, Michael Stahnke, and the mystagogical Tom Bereknyei. Along the way, old friends Dylan Storey and Larry “Catfish” Murdock offered invaluable assistance. K8s isn’t my bag, nor is Nix—yet!—and I rely on their help for … too much.