Focusing on the Code, Not the Cruft
As a developer advocate, I’m used to spending a lot of time worrying about whether a demo will work anywhere but my machine. The unknown part rarely concerns CPU, memory, or bandwidth constraints. It almost always comes down to reproducing the same development environment across macOS and Linux… with the same runtimes, native dependencies, services, package versions, and behaviors. Getting everything installed, configured, documented, and running can easily overshadow the thing I actually want to show.
As luck would have it, I work at Flox, where we address this problem by defining development environments as code. You can use Flox to capture the runtimes, packages, variables, hooks, and services a project needs, then activate the same reproducible environment on macOS and Linux, x86-64 or ARM, with platform-specific GPU support if available. An environment created on Apple Silicon, for example, can be shared with a colleague working on Intel-based Linux, then carried into CI in the cloud… without reconstructing it at each stage.
Flox ships two agent skills that automate much of that work:
flox:floxbuilds a Flox environment for a new project from the requirements you give your coding agent.flox:floxifyanalyzes an existing repository, identifies what it already depends on, and builds a Flox environment to match.
I tried both with Claude Code on an Apple Silicon Mac. This article chronicles my experience, warts and all.
Building a New Project with flox
As a relatively new Flox employee, I was intrigued to see how well the flox skill captures my employer’s best practices. Unlike a static tutorial or demo, the flox skill isn’t fixed to a snapshot in time: it can evolve as packages, tooling, and recommended patterns change.
This is especially useful for me because I need to create countless demos and prebuilt harnesses that demonstrate the mechanics of reusable development environments.
For the flox skill’s greenfield case, I asked Claude to build a small FastAPI service that accepts text, embeds it using Python sentence-transformers, and returns the vector.
❯ I'd like to use Flox to create a simple embedding endpoint with FastAPI and sentence-transformers. A POST to the api with a text payload is converted into a vector and returned. I’d also like to serve it over HTTPS locally for development and testing. What open source libraries and tools would you recommend?
● Flox is installed here. Declarative .flox/env/manifest.toml gives a reproducible env including Python itself and any system libs; good if you want flox activate to be the only setup step.And off Claude Code goes into planning mode with the flox skill!
I watched in real time as the skill selected Python v3.13 and also verified that any required Python packages were available in the Flox catalog. The skill “knew” to search for python313Packages.sentence-transformers because this name is consistent with the nixpkgs convention for packages built against a particular Python version. In the same way, the skill knew to select python313Packages.fastapi (for FastAPI)… and to do the same for any other Python v3.13-compatible packages. When it finished, it had automatically populated the Flox manifest with the following dependencies:
[install]
python.pkg-path = "python313"
python.pkg-group = "python"
fastapi.pkg-path = "python313Packages.fastapi"
fastapi.pkg-group = "python"
uvicorn.pkg-path = "python313Packages.uvicorn"
uvicorn.pkg-group = "python"
sentence-transformers.pkg-path = "python313Packages.sentence-transformers"
sentence-transformers.pkg-group = "python"
pydantic.pkg-path = "python313Packages.pydantic"
pydantic.pkg-group = "python"
openssl.pkg-path = "openssl"When I pressed Claude as to why it had selected Python v3.13 instead of an older version, it gave the rationale that current releases of Sentence Transformers require a minimum of Python v3.10 and don’t officially support Python v3.14. In other words, with an EOL date of October 2026, Python v3.10 would have been a stale choice for this project… while v3.14 is still too new. Python v3.13 has been available for two years now and is well supported by Sentence Transformers. Claude settled on a logical compromise between too-old and too-new.
The shared pkg-group isolates the Python dev toolchain inside its own dependency-resolution boundary. This makes sense: Flox automatically resolves dependencies and selects versions that can coexist with one another; Package Groups give us a way to segregate packages that must be resolved and/or upgraded together from those that can move/upgrade independently. Here, Python and its Python packages form one group, while native tools such as openssl, used for local HTTPS, remain in Flox’s default, or “top-level,” group.
By isolating Python dev dependencies in their own group, a version constraint or dependency conflict in a native tool (like openssl) doesn’t unnecessarily constrain the Python stack to use older, compatible historical packages (like Python 3.12), or vice versa.
Putting It All Together
The skill didn’t stop there. It created the complete environment that the FastAPI service needs to run:
- Defined the model and port. Sets the Sentence Transformers model and the port the API listens on.
- Kept model caches inside the Flox environment. Redirects the Hugging Face and Sentence Transformers caches to
$FLOX_ENV_CACHE, so model data stays with the environment cache. - Preloaded the embedding model. Downloads and initializes the
all-MiniLM-L6-v2model on first activation, so the first real request to the /embed endpoint is fast. - Defined the API as a Flox service. Runs the FastAPI application with Uvicorn on the configured port. This exposes an endpoint to feed text into the embedding encoder.
On activation, the shell moved from Python v3.9.6 (installed on my Mac) to the environment's v3.13.15…
$ python3 --version
Python 3.9.6
$ flox activate -s
✔ You are now using the environment 'FastAPI'
To stop using this environment, run 'flox deactivate'
$ python3 --version
Python 3.13.15… and the API worked the first time I tried it:
$ curl -X POST localhost:8000/embed \
-H 'content-type: application/json' \
-d '{"input": ["hello world"]}'
{
"model":"sentence-transformers/all-MiniLM-L6-v2",
"dimensions":384,
"embeddings":[
[-0.03447723388671875,
0.031023329123854637,
0.006735051982104778, ...]
],
"app":"flox-embed"
}The flox skill facilitated a boilerplated, best-practice instance of my desired environment for a new demo! And most importantly, there was a conversation with Claude that outlined the reasoning for the version choices, various additional packages I could use if I wanted to save and serve the embedded vectors, and code implementation in the hook section of manifest.toml. This makes me a better Flox user by exploring what is being built out to support my environment.
floxify-ing an Existing Repo
If the flox skill is for the demo you haven't written yet or a greenfield project, floxify is for the codebase you already have. And the repo I picked is not a small one.
Mastodon combines Ruby on Rails, Node, a separate streaming server, Sidekiq, PostgreSQL, Redis, Vite, and several native dependencies. Its dependency information lives across .ruby-version, .nvmrc, Gemfile.lock, package.json, Dockerfile, Aptfile, docker-compose.yml, and Procfile.dev. That is the kind of repository where setting up your dev environment becomes a software project in its own right.
I asked Claude to floxify the fresh checkout.
The first thing floxify does is refuse to guess. The skill starts by running a deterministic analyzer across every known dependency, runtime, service, and build file, then uses those results to guide Claude as it generates the Flox environment’s manifest. The skill then runs a second script to validate the manifest against its original findings. The manifest isn’t considered final until both agree with one another. Watching your own project scroll past, one line per file, is the trust-building exercise I wish was built into most agent skills before batch manipulation:
Scanning mastodon/ detecting runtimes, services, and build tools...
.ruby-version ruby 4.0.6
.nvmrc node 24.19
Gemfile.lock ruby 4.0.6 · bundler 4.0.18 · pg · ruby-vips · redis → 341 gems
package.json yarn@4.18.0 · workspaces [".", "streaming"] · node >=22
docker-compose.yml postgres:14-alpine · redis:7-alpine · (es: commented out)
Dockerfile ruby 4.0.6 · node 24 · libvips 8.18.5 · ffmpeg 9.0.1 · libicu76 · libidn12
Aptfile libidn12
Procfile.dev web · sidekiq · stream · vite
Found: Ruby 4.0.6 (← .ruby-version) · Node 24.19 (← .nvmrc) · Yarn 4.18.0 (← package.json)
· PostgreSQL 14 · Redis 7 (← docker-compose.yml) · libvips · ffmpeg · ICU · libidn (← Dockerfile / Aptfile)To take just one example, ffmpeg does not appear in the Gemfile.lock. Mastodon builds it from source in the Dockerfile and copies it into the runtime image. A Ruby-only dependency scan would miss it.
The skill then checked the Flox catalog for the requested runtimes and native packages. It also inspected each package’s default outputs. That’s how it caught a detail I would likely have missed were I doing this on my own: installing a package doesn’t automatically install all of its shared objects and development headers.
It’s like this: Nix packages can split their contents into separate outputs: scripts and binaries, shared libraries, development headers, documentation, and so on. Nix distributes a package’s outputs according to the same functional boundaries represented by FHS directories like /usr/bin, /usr/lib, /usr/include, etc. This way, if you’re creating a Nix runtime environment, it’s easy to skip development headers, static libraries, build-system metadata, and other files that you only need if you’re building or developing against the package.
Flox installs the package’s default outputs unless you explicitly declare others. For Mastodon, several native gems need more than the defaults: pg needs PostgreSQL’s libraries and headers, ruby-vips needs libvips, and other C extensions need their corresponding development files. The skill detected those requirements and declared the additional outputs:
[install]
# --- Language runtimes ---
ruby.pkg-path = "ruby_4_0" # .ruby-version 4.0.6 · Gemfile.lock RUBY VERSION 4.0.6 · Dockerfile RUBY_VERSION=4.0.6
ruby.version = "4.0.6" # exact match in catalog (Gemfile allows >= 3.3.0, < 4.1.0)
nodejs.pkg-path = "nodejs_24" # .nvmrc 24.19 · Dockerfile NODE_MAJOR_VERSION=24 · package.json engines.node ">=22"
nodejs.version = "24.19.0" # exact match to .nvmrc 24.19
# --- Package managers ---
yarn.pkg-path = "yarn-berry" # package.json packageManager "yarn@4.18.0"; catalog max 4.14.1 (same Yarn 4 line)
# bundler ships with ruby_4_0 (Gemfile.lock BUNDLED WITH 4.0.18)
# --- Datastores (both hard runtime deps; wired as [services.*] below) ---
postgresql.pkg-path = "postgresql_14" # docker-compose.yml db image postgres:14-alpine · also builds the pg gem
postgresql.outputs = ["out", "lib", "dev"] # default is out+man only; pg gem needs pg_config (out) + libpq (lib) + headers (dev)
redis.pkg-path = "redis" # docker-compose.yml redis image redis:7-alpine
# --- Native media libraries (runtime + gem C-extensions) ---
vips.pkg-path = "vips" # Gemfile ruby-vips ~> 2.2 · Dockerfile VIPS_VERSION=8.18.5
vips.outputs = ["out", "bin", "dev"] # default is bin+man only; libvips shared lib lives in `out`, headers in `dev`
ffmpeg.pkg-path = "ffmpeg" # Dockerfile FFMPEG_VERSION=9.0.1 (catalog 9.0.1, exact) · video transcoding
# --- Native C-extension gem dependencies ---
icu.pkg-path = "icu" # Gemfile charlock_holmes ~> 0.7.7 links system ICU · Dockerfile libicu76
icu.outputs = ["out", "dev"]
libidn.pkg-path = "libidn" # Gemfile idn-ruby · Aptfile libidn12 · Dockerfile libidn-dev (GNU libidn v1 — NOT libidn2)
libidn.outputs = ["out", "dev"]
# --- Toolchain to compile native gems (pg, ruby-vips, charlock_holmes, idn-ruby, ...) ---
pkg-config.pkg-path = "pkg-config"
gcc.pkg-path = "gcc"
gnumake.pkg-path = "gnumake"The Flox environment explicitly declares versions of Ruby and Node because Mastodon itself pins these. With only two pinned dependencies, Flox’s solver has a lot more room to resolve a coherent set of compatible packages. Once it does, it records the versions of every resolved package in a lockfile, so the environment always resolves to exactly the same dependencies. If necessary I could also explicitly declare versions for each and every package in the manifest… or have the Flox skill do it for me. This would prevent someone running flox upgrade from changing versions.
How the Skill Works Its Magic
One wart was introduced by the Mastodon Dockerfile’s Alpine base image, which pins Redis to v7.2.7. The floxify skill attempted to do this, too, but Flox’s solver couldn’t find a common catalog snapshot containing Redis v7.2.7 alongside Ruby v4.0.6 and Node v24.19. (The Flox Catalog contains Redis v7.2.7; however, no single nixpkgs git commit pins compatible versions of Redis v7.2.7, Ruby v4.0.6, and Node v24.19. It’s kind of like Netflix streaming three of your favorite movies in the same calendar year, but never all three at the same time.)
Because Mastodon only requires Redis 7 rather than that specific patch release, Claude dropped the unnecessary pin and let Flox resolve a compatible version on its own.
That was one valid solution, but Claude could also have isolated Redis v7.2.7 in its own package group. When I asked Claude why it had made this decision, it told me that floxify has a pre-defined escalation ladder. In this case, the first rung was the most pragmatic choice: keep any required pins and relax any non-essential version constraints. Mastodon doesn’t need the specific Redis patch version pinned, so dropping this pin was sufficient.
Verification also surfaced two other missing pieces. The first is that native gems like openssl and hiredis-client need to compile against OpenSSL development headers. Mastodon’s Docker image gets OpenSSL from its Ruby base image, so that dependency is easy to miss when reconstructing the environment from the repo. Claude added OpenSSL explicitly and requested its out and dev outputs so the libraries and headers would be available during gem compilation. I didn’t have to lift a finger to discover or address this.
The final issue was macOS-specific and came down to the #!/usr/bin/env ruby shebang already present in Mastodon’s ./bin/rails script. Flox normally makes shared libraries available on macOS using DYLD_* search paths, but /usr/bin/env is an Apple SIP-protected system executable. When the shebang invokes it, SIP strips the DYLD_* variables before env launches Ruby, so Ruby does not inherit Flox’s DYLD_* search path and ruby-vips cannot find libvips. Claude worked around this by adding $FLOX_ENV/lib to LD_LIBRARY_PATH, which Ruby FFI can use as a fallback search path when locating the library.
floxify verifies the environment at several points. Before Claude writes the manifest, it checks package names, versions, outputs, and per-system compatibility against the Flox catalog. Afterward, a hard gate tests package resolution, runs the environment and its hooks, performs functional import checks for Python projects, and compares the manifest against the analyzer’s original findings. Any blocking failure sends Claude back to fix the manifest, after which it reruns the verification gate until all checks pass.
From Dependencies to a Runnable Application
In the finalized manifest, there’s a clear division of responsibilities: Flox provides the system-level dependencies: Ruby, Node, PostgreSQL, Redis, compilers, and native libraries; the ecosystem-specific package managers, Bundler and Yarn, handle Mastodon’s Ruby and JavaScript dependencies.
The Claude-generated activation hook keeps Ruby gems, database state, and related caches inside the Flox environment’s path. PostgreSQL and Redis run as Flox services. I then asked Claude to map Mastodon’s four Procfile.dev processes (Rails, Sidekiq, the streaming server, and Vite) to Flox services as well.
This gave me a six-service development stack I could start automatically at activation:
$ flox activate -s
$ flox services status
postgres Running
redis Running
sidekiq Running
streaming Running
vite Running
web Running
$ curl localhost:3000/health
OK
$ curl localhost:4000/api/v1/streaming/health
OKThe local Mastodon stack now comes up from the Flox environment itself, without relying on Docker Compose, Homebrew, or a scripted sequence of setup commands from the README. More importantly, I now have the ability to version and manage the reproducible Flox runtime environment alongside the Mastodon code: the manifest and lockfile travel with the project itself, so supported Linux and Apple Silicon systems always activate the same deterministic dependency set… rather than rebuilding from scratch.
What I Learned from flox and floxify
Each skill approaches a similar problem from completely different directions.
-
floxstarts with what you want to build and constructs the environment to support it. It encoded best practices into a manifest I would not have written as carefully by hand. -
floxifystarts with an existing codebase, reconstructs the environment from the evidence already present in the repo, and verifies the result before handing it back.
Both skills provide guardrails, context, package-specific guidance, fallbacks, and codified escalation paths that Claude can use to identify runtimes, dependencies, package outputs, and version constraints; define Flox-managed services; account for platform-specific differences; and validate that the manifest.toml resolves successfully with Flox and matches the requirements declared by the project. The result was that I spent zero time reverse-engineering how to build and run Mastodon and had a working environment in just minutes.
The floxify skill, in particular, performed archaeology and paved the road to getting Mastodon configured properly. It found a runtime dependency that lives only in a Dockerfile, recognized that Debian’s libidn12 package maps to GNU libidn, not the similarly named libidn2, honored a commented-out Compose service, and declared non-default package outputs that I did not know existed… until Claude explained them to me.
Several fixes were surfaced by the floxify skill's own verification gate refusing to sign off until flox activate actually worked, and one of them, the SIP behavior, has nothing to do with Flox and everything to do with the macOS machine I happened to be clanking on.
This uncovered a nuance that’s worth bringing out. The skill’s verification loop runs the Flox environment, reads the errors, and fixes the manifest until the environment runs, and tells me exactly what it changed and why… in comments that will still be there when I revisit this repo in a year and have forgotten all of it.
Next stop: flox push, and putting this instance somewhere with a real domain so it can meet the rest of the fediverse.
Update: With even more floxify skill applied to the deployment and ops infrastructure, I now have Floxtodon.dev up and running! This is the actual Mastodon repo with the environment management converted over to Flox. Head over and check it out!
Get the skills → https://github.com/flox/flox-skills
Read the docs → https://flox.dev/docs
Download Flox and point your coding agent at your own repo.


