# Focusing on the Code, Not the Cruft

> Putting the flox and floxify agent skills through their paces with Claude Code: building a FastAPI embedding service from scratch, then reconstructing a complete six-service Mastodon development environment from an existing checkout.

Source: https://flox.dev/blog/focusing-on-the-code-not-the-cruft/
Author: Justin Castilla
Published: 2026-10-01

## Summary

The **`flox`** and **`floxify`** [agent skills](https://github.com/flox/flox-skills) let a coding agent build reproducible Flox environments: **`flox`** creates one for a new project from your requirements, while **`floxify`** reconstructs one from an existing repo and verifies it. Tested with Claude Code on a Mac, **`flox`** produced a working FastAPI embedding service, and **`floxify`** turned a Mastodon checkout into a six-service dev stack in minutes.

Key takeaways:

- **`flox`** builds an environment for a new project from your requirements; **`floxify`** reconstructs one from an existing repo.
- **`flox`** turned a plain-language request into a working FastAPI service, and explained the version and packaging choices it made.
- **`floxify`** scans the repo’s dependency, runtime, service, and build files first, then validates the manifest against those findings.
- Its verification loop caught missing dependencies and platform quirks a manual setup could easily miss, and fixed them.
- Mastodon’s databases, workers, and app processes all ran as Flox services, without Docker Compose or Homebrew.

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](https://github.com/flox/flox-skills) that automate much of that work:

- **`flox:flox`** builds a Flox environment for a new project from the requirements you give your coding agent.
- **`flox:floxify`** analyzes 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.

```text
❯ 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](https://github.com/nixos/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](https://flox.dev/docs/man/manifest.toml) with the following dependencies:

```toml
[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](https://flox.dev/docs/concepts/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-v2`** model 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…

```console
$ 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:

```console
$ 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](https://flox.dev/docs/man/manifest.toml#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](https://github.com/mastodon/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:

```console
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](https://flox.dev/docs/tutorials/package-outputs#selecting-package-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](https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard) 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:

```toml
[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](https://flox.dev/docs/concepts/environments#manifest-lock), 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](https://hub.flox.dev/packages/base/redis); 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](https://support.apple.com/en-us/102149)-protected system executable. When the shebang invokes it, [SIP strips the **`DYLD_*`** variables](https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/RuntimeProtections/RuntimeProtections.html) 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:

```console
$ 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
OK
```

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

- **`flox`** starts 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.

- **`floxify`** starts 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](https://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](https://github.com/justincastilla/mastodon-flox).

Get the skills → [https://github.com/flox/flox-skills](https://github.com/flox/flox-skills)

Read the docs → [https://flox.dev/docs](https://flox.dev/docs)

[Download Flox](https://flox.dev/download/) and point your coding agent at your own repo.

---

## Frequently Asked Questions

### What are the `flox` and `floxify` agent skills?

They are two agent skills, published in the [flox-skills repository](https://github.com/flox/flox-skills), 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](https://flox.dev/blog/focusing-on-the-code-not-the-cruft/#what-i-learned-from-flox-and-floxify))

### When should I use `flox`, and when should I use `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](https://flox.dev/blog/focusing-on-the-code-not-the-cruft/#what-i-learned-from-flox-and-floxify))

### How does `floxify` work out what an existing project needs?

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](https://flox.dev/blog/focusing-on-the-code-not-the-cruft/#floxify-ing-an-existing-repo))

### How do the skills make sure the environment actually works?

**`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](https://flox.dev/blog/focusing-on-the-code-not-the-cruft/#how-the-skill-works-its-magic))

### What happens when the agent runs into a dependency conflict?

**`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](https://flox.dev/blog/focusing-on-the-code-not-the-cruft/#how-the-skill-works-its-magic))

### Do I still need Docker Compose or Homebrew to run a project’s services?

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](https://flox.dev/blog/focusing-on-the-code-not-the-cruft/#from-dependencies-to-a-runnable-application))

### How does a Flox environment stay the same across machines and teammates?

The environment is declared in a manifest, and Flox records the exact version of every resolved package in a [lockfile](https://flox.dev/docs/concepts/environments#manifest-lock). 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](https://flox.dev/blog/focusing-on-the-code-not-the-cruft/#from-dependencies-to-a-runnable-application))

---

This is the Markdown version of https://flox.dev/blog/focusing-on-the-code-not-the-cruft/. An index of everything on flox.dev is at https://flox.dev/llms.txt, and reference documentation is at https://flox.dev/docs/llms.txt.
