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! The floxified Mastodon repo can be found here.
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.
Frequently Asked Questions
They are two agent skills, published in the flox-skills repository, that help a coding agent such as Claude Code build Flox environments. A Flox environment captures the runtimes, packages, variables, hooks, and services a project needs, so the same setup can be activated on macOS and Linux. The skills give the agent guardrails, package-specific guidance, fallbacks, and verification steps instead of leaving it to guess. (What I Learned from flox and floxify)
Use flox when you’re starting something new: you describe what you want to build, and it constructs an environment to support it. Use floxify when you already have a codebase: it reconstructs the environment from evidence already in the repo, then verifies the result before handing it back. In the article, flox set up a new FastAPI service, and floxify set up an existing Mastodon checkout. (What I Learned from flox and floxify)
It starts by refusing to guess. A deterministic analyzer scans the repo’s dependency, runtime, service, and build files, and the agent writes the Flox manifest from those findings. A second script then checks the manifest against the original findings. Reading every source matters: in Mastodon, one native dependency appeared only in the Dockerfile, so a scan of the Ruby dependencies alone would have missed it. (floxify-ing an Existing Repo)
floxify verifies at several points. Before writing the manifest, it checks packages and platform compatibility against the Flox catalog. Afterward, a verification gate resolves the packages, runs the environment and its hooks, and compares the result with the original analysis. Any blocking failure sends the agent back to fix the manifest until every check passes, and the agent leaves comments explaining what it changed and why. (How the Skill Works Its Magic)
floxify follows a predefined escalation ladder. Its first step is to keep the version pins a project truly requires and relax the ones that aren’t essential. In the article, one service was pinned to an exact release that couldn’t be resolved alongside the other runtimes. Because the project only needed the major version, the agent dropped the extra pin and let Flox choose a compatible release. (How the Skill Works Its Magic)
Not in the article’s example. Databases and the application’s own processes can all run as Flox services. For Mastodon, PostgreSQL, Redis, the web server, background workers, the streaming server, and the frontend build server all started together with flox activate -s, without Docker Compose, Homebrew, or a list of setup commands from the README. (From Dependencies to a Runnable Application)
The environment is declared in a manifest, and Flox records the exact version of every resolved package in a lockfile. Both are versioned alongside the project’s code, so supported Linux and Apple Silicon systems activate the same dependency set instead of rebuilding it. An environment created on one machine can be shared with a colleague and carried into CI without being reconstructed at each stage. (From Dependencies to a Runnable Application)
Keep learning with Flox
Interviews, tips, guides, industry best practices, and news.
Making Flox Better for People and Machines
Flox v1.14.1 improves activation, search, and usability as more developers, CI systems, and AI agents rely on Flox.
Read postFlox Account Tokens for People and Machines
Flox now supports granular, role-based tokens that enable organizations to reduce their blast radius, keep pipelines running smoothly through staff changes, easily rotate credentials on a schedule, and seamlessly authenticate in browserless workflows.
Read postOne Tool, Every Language: Declarative Dev Environments
Imagine creating declarative, reproducible environments for the toolchains and dependencies you use in your workflows. Declaring compilers, debuggers, dev libraries, and other dependencies as code. Declarative patterns make this convenient, reproducible, and scalable.
Read postAI Stack as Code: Define It Once, Run It Everywhere
Learn how AI stacks as code make ML and AI environments reproducible across macOS, Linux, Windows, CUDA, and Apple Silicon with reusable reference patterns.
Read postPlatform Teams and the Challenge of Works-on-My-Machine
A practical guide to creating and working with Flox standardized local development environments, including playbooks for defining, reproducing, and managing project environments at scale.
Read postRun Frontier Models Anywhere with DeepSeek TUI
CLIs like DeepSeek TUI gives users a fast, terminal-native harness for running local and cloud models on NVIDIA CUDA and Apple Silicon. This article explores how Nix and Flox make it straightforward to run, build, and share the stacks we use to run models locally: model servers, CLI harnesses, and frontier checkpoint models like Qwen 3.6.
Read post


