nixfredFLOXFIELD GUIDE · BY NIX UNOFFICIAL
LEARN / THE ARCHITECTURE

The Flox architecture

Flox has five moving parts: a declarative manifest, a lockfile that pins it, a resolver backed by the Flox Catalog, packages that live as closures in the Nix store, and an activation step that points your shell at them. FloxHub adds a hosted copy of an environment with numbered generations. There is no container and no VM in the loop, which is why activation is fast and why Flox is a different thing from Docker.

This page is drawn from https://flox.dev/docs/concepts/environments, https://flox.dev/docs/concepts/activation, https://flox.dev/docs/concepts/package-groups, https://flox.dev/docs/concepts/packages-and-catalog, https://flox.dev/docs/concepts/base-catalog, https://flox.dev/docs/concepts/floxhub-environments, https://flox.dev/docs/concepts/generations, https://flox.dev/docs/concepts/data-collection, https://flox.dev/docs/man/flox-activate, https://github.com/flox/flox and the repo README at https://raw.githubusercontent.com/flox/flox/main/README.md. Docs change often; the manifest schema section in https://flox.dev/docs/man/manifest.toml shows which CLI version introduced each feature.

The pipeline, end to end

flowchart LR
  M[manifest.toml<br/>what you want] -->|resolve| R[Resolver]
  C[(Flox Catalog<br/>Base Catalog plus custom)] -.->|revisions and metadata| R
  R --> L[manifest.lock<br/>exact versions and store paths]
  L -->|materialize closure| S[(Nix store<br/>/nix/store)]
  S --> E[FLOX_ENV<br/>merged symlink forest]
  E -->|flox activate| SH[Configured shell<br/>PATH, vars, hook, profile]
  H[(FloxHub<br/>generations)] <-.->|push and pull| M
Whiteboard diagram (rendered from the source mermaid)

In words: you edit the manifest (usually with flox edit or flox install); Flox resolves it against the catalog and writes the result to manifest.lock; the packages are built or fetched into the Nix store; and flox activate sets variables so the packages are on your PATH (https://flox.dev/docs/concepts/environments, https://flox.dev/docs/concepts/activation). When you run flox install, flox edit, flox push, flox pull, or flox activate, “Flox attempts to resolve the environment and materialize its closure at $FLOX_ENV if it has not already done so” (https://flox.dev/docs/concepts/package-groups).

Layer 1: manifest.toml and manifest.lock

A Flox environment keeps its metadata, manifest and lock file in a .flox directory wherever flox init was run (https://flox.dev/docs/concepts/environments). The files:

FileWhat it isWho writes itSource
.flox/env/manifest.tomlTOML declaration of packages, vars, hook, profile, services, build and optionsYou, best through flox edit, which validates on savehttps://flox.dev/docs/concepts/environments
.flox/env/manifest.lockSnapshot of specific package versions and dependencies that were built and activatedFloxhttps://flox.dev/docs/concepts/environments
.flox/env.jsonName and version of the environmentFloxhttps://flox.dev/docs/concepts/environments
.flox/pkgsNix expression buildsYou, if you use themhttps://flox.dev/docs/concepts/environments

The lockfile records, per package, an install_id, a version, a system, and the outputs as store paths such as /nix/store/...-nodejs-24.0.1 (https://flox.dev/docs/concepts/environments). Grouped packages also record a group and the catalog rev, which is a nixpkgs commit (https://flox.dev/docs/concepts/package-groups). Say: “The manifest is what you asked for. The lockfile is what you got, and it is what makes the same environment come back on another machine.”

Layer 2: the Flox Catalog and the resolver

“A Flox Catalog is a collection of package artifacts and associated metadata that can be consumed via a Flox Environment” (https://flox.dev/docs/concepts/packages-and-catalog). There are two kinds (https://flox.dev/docs/concepts/packages-and-catalog):

  • Base Catalog. Populated by Flox from the Nix Community’s Nixpkgs collection.
  • Custom Catalogs. Maintained by users and organizations through flox publish, public or private.

The Base Catalog is generated from a fork of nixpkgs, holds historical metadata such as versions and system support over time, and is updated on an automated schedule (https://flox.dev/docs/concepts/base-catalog). It ingests the equivalent of the nixos-unstable branch daily (https://flox.dev/docs/concepts/base-catalog). Flox says it samples that branch “at the same time daily” into a non-destructive fork, and a “catalog revision” is a snapshot of a subset of packages at a specific nixpkgs commit (https://flox.dev/docs/concepts/package-groups).

How resolution works, per package group (https://flox.dev/docs/concepts/package-groups):

  1. Collect every descriptor in the group (pkg-path, version constraints, systems).
  2. Search catalog revisions of the group’s stability, newest first, for one revision that satisfies all packages.
  3. Apply per-package filters: version, broken, unfree, systems.
  4. Write each package to manifest.lock with the same rev.

All packages in one group share a nixpkgs commit; different groups may use different commits. The default group is toplevel (https://flox.dev/docs/concepts/package-groups). The resolver is a network service: flox install resolves packages from the Flox Catalog, which requires a free FloxHub account (https://flox.dev/docs/man/flox-install). Catalog traffic goes to api.flox.dev, and packages come from cache.nixos.org or cache.flox.dev (https://flox.dev/docs/concepts/floxhub).

Layer 3: the closure in the Nix store

Each package lives at /nix/store/<hash>-<name>-<version>/, where the hash is derived from all of the package’s declared build inputs, so two versions of one library can coexist (https://flox.dev/docs/concepts/package-groups). Nix-built binaries carry absolute RUNPATH entries to their own libraries (https://flox.dev/docs/concepts/package-groups). A package plus everything it depends on is its transitive closure (https://flox.dev/docs/concepts/manifest-builds).

Flox then builds one derivation that is a directory of symlinks pointing into the Nix store, merged across all packages and all groups. Package groups are a resolution-time concept and “In the materialized environment, package groups don’t exist” (https://flox.dev/docs/concepts/package-groups). File collisions are settled by priority, where lower numbers win (https://flox.dev/docs/concepts/package-groups). That merged directory is $FLOX_ENV (https://flox.dev/docs/man/flox-activate).

Ask: “Is the same dependency needed at two different versions?” If yes, the answer is a package group, not a container (https://flox.dev/docs/concepts/package-groups).

Layer 4: activation

Activation “works by configuring a shell” (https://flox.dev/docs/concepts/activation). Four methods exist (https://flox.dev/docs/concepts/activation):

ModeCommandhook.on-activateprofile
Subshellflox activaterunsruns
In-placeeval "$(flox activate)"runsruns
Shell commandflox activate -crunsruns
Execflox activate --runsskipped

The timeline for a subshell (https://flox.dev/docs/concepts/activation): flox activate execs a Bash script bundled with Flox; it runs setup, sets your [vars], sources each package’s profile.d scripts (plugins, which the docs call experimental), sources hook.on-activate, and then execs your shell (the FLOX_SHELL variable overrides which one), which sources the [profile] scripts. The hook is always Bash and cannot define aliases for your shell; profile scripts run in your shell and can (https://flox.dev/docs/concepts/activation).

Two runs of flox activate attach to one activation: Flox records the environment variables from the first activation and applies them to later ones, so hook.on-activate runs for the first one only, and hook.on-deactivate runs when the last one detaches (https://flox.dev/docs/concepts/activation). The docs sum it up: “There’s no container to build and there’s no VM to boot up.” (https://flox.dev/docs/concepts/activation)

Variables Flox sets

From https://flox.dev/docs/man/flox-activate:

VariableMeaning
FLOX_ENVPath to the built environment, with merged bin, lib and so on
FLOX_ENV_CACHEA directory for transient hook files; kept locally, not pushed
FLOX_ENV_PROJECTThe project directory; for flox activate -r it is the current working directory
FLOX_ENV_DESCRIPTIONThe project name of the environment
FLOX_PROMPT_ENVIRONMENTSSpace-delimited names of active environments
FLOX_ACTIVATE_START_SERVICES"true" if this activation started services
_FLOX_ACTIVE_ENVIRONMENTSJSON array of active environments; “an implementation detail” whose contents may change

Variables you can set to steer it: FLOX_SHELL, FLOX_PROMPT and FLOX_PROMPT_COLOR_{1,2} (https://flox.dev/docs/man/flox-activate). Activation also puts the environment’s .flox/run/<system>.<env>.dev/bin at the front of PATH (https://flox.dev/docs/concepts/activation).

Environment types and FloxHub

TypeWhere it livesCreated bySource
Path environmentA local .flox directoryflox inithttps://flox.dev/docs/concepts/environments
Centrally managed (FloxHub)FloxHub, with a cached local copyflox push on a path environmenthttps://flox.dev/docs/concepts/environments
Remote useThe cached copy of a FloxHub environmentflox activate -r owner/namehttps://flox.dev/docs/concepts/floxhub-environments

A FloxHub environment has an “upstream” copy (the source of truth) and a “local” copy (https://flox.dev/docs/concepts/floxhub-environments). Commands such as flox install -r operate on the local cache and can work offline, and the CLI warns when local and upstream differ, telling you to flox push or flox pull (https://flox.dev/docs/concepts/floxhub-environments).

Generations are version numbers of an environment on FloxHub. The first is created by flox push; new ones are created whenever a command such as flox install or flox edit changes it (https://flox.dev/docs/concepts/generations). flox generations rollback undoes a change without creating a new generation, and flox generations history shows which generation was live over time (https://flox.dev/docs/concepts/generations). Say: “Generations are git-like change management for the environment, not for your source code.” The docs make the same comparison: “it’s similar to git, but tailored for environments” (https://flox.dev/docs/concepts/generations).

The CLI repository

The CLI is open source at https://github.com/flox/flox under the GPLv2 (https://raw.githubusercontent.com/flox/flox/main/README.md). The top level (https://api.github.com/repos/flox/flox/contents) holds cli, buildenv, resolver, package-builder, mkContainer, ld-floxlib, nix-plugins, modules, pkgs, checks, docs, test_data, flake.nix, Cargo.toml and a Justfile. The cli directory (https://api.github.com/repos/flox/flox/contents/cli) holds crates and data including flox, flox-rust-sdk, flox-manifest, flox-activations, flox-config, flox-core, flox-events, floxhub-client, catalog-api-v1, shell_gen, schemas, tests and mk_data.

What the sources state about those directories: cli/flox-events defines the telemetry events and the pipeline that buffers and sends them, integration tests live in cli/tests, and cli/mk_data generates mock catalog responses (https://github.com/flox/flox/blob/main/CONTRIBUTING.md, https://raw.githubusercontent.com/flox/flox/main/cli/flox-events/README.md). What each other directory does is not described in the pages read; the names suggest a manifest parser, an activation runtime and a catalog client, but treat that as a guess [verify] before saying it to a customer.

Telemetry

The CLI “sends Flox usage telemetry and error reports”, each carrying a random device ID that Flox links to your FloxHub account when you sign in (https://flox.dev/docs/concepts/data-collection). Events hold the command name, success, duration, Flox version, OS, CPU architecture and shell, plus search terms, package names and environment names; they do not hold hostnames, error messages, the commands you run inside an environment, or your manifest’s variables and scripts (https://flox.dev/docs/concepts/data-collection). Telemetry is on by default with a first-run notice (https://raw.githubusercontent.com/flox/flox/main/cli/flox-events/README.md).

To turn it off:

export FLOX_DISABLE_METRICS=true      # shell profile, CI, or before first run
flox config --set disable_metrics true
curl -fsSL https://get.flox.dev | FLOX_DISABLE_METRICS=true sh

(https://flox.dev/docs/concepts/data-collection). The variable accepts only true or false, and any other value such as 1 makes commands exit with an error (https://flox.dev/docs/concepts/data-collection). Turning it off does not stop requests that features need, such as search and install, nor the background upgrade check when signed in (https://flox.dev/docs/concepts/data-collection). A user can override these settings, so to enforce them the docs say to block api.flox.dev/events, z7qixlmjr3.execute-api.eu-north-1.amazonaws.com and *.ingest.us.sentry.io at the network edge (https://flox.dev/docs/concepts/data-collection). The CLI still sends telemetry when pointed at a self-hosted FloxHub (https://flox.dev/docs/concepts/data-collection).

Related to privacy: when you push an environment, FloxHub stores the manifest and lockfile for every generation, including [vars] values and hook scripts, and makes pushed environments public by default, so “keep secrets out of a manifest you push” (https://flox.dev/docs/concepts/data-collection).

Questions to ask a customer

← Nix 101 for Flox people · Environments and the manifest →