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
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:
| File | What it is | Who writes it | Source |
|---|---|---|---|
.flox/env/manifest.toml | TOML declaration of packages, vars, hook, profile, services, build and options | You, best through flox edit, which validates on save | https://flox.dev/docs/concepts/environments |
.flox/env/manifest.lock | Snapshot of specific package versions and dependencies that were built and activated | Flox | https://flox.dev/docs/concepts/environments |
.flox/env.json | Name and version of the environment | Flox | https://flox.dev/docs/concepts/environments |
.flox/pkgs | Nix expression builds | You, if you use them | https://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):
- Collect every descriptor in the group (
pkg-path, version constraints, systems). - Search catalog revisions of the group’s stability, newest first, for one revision that satisfies all packages.
- Apply per-package filters:
version,broken,unfree,systems. - Write each package to
manifest.lockwith the samerev.
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):
| Mode | Command | hook.on-activate | profile |
|---|---|---|---|
| Subshell | flox activate | runs | runs |
| In-place | eval "$(flox activate)" | runs | runs |
| Shell command | flox activate -c | runs | runs |
| Exec | flox activate -- | runs | skipped |
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:
| Variable | Meaning |
|---|---|
FLOX_ENV | Path to the built environment, with merged bin, lib and so on |
FLOX_ENV_CACHE | A directory for transient hook files; kept locally, not pushed |
FLOX_ENV_PROJECT | The project directory; for flox activate -r it is the current working directory |
FLOX_ENV_DESCRIPTION | The project name of the environment |
FLOX_PROMPT_ENVIRONMENTS | Space-delimited names of active environments |
FLOX_ACTIVATE_START_SERVICES | "true" if this activation started services |
_FLOX_ACTIVE_ENVIRONMENTS | JSON 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
| Type | Where it lives | Created by | Source |
|---|---|---|---|
| Path environment | A local .flox directory | flox init | https://flox.dev/docs/concepts/environments |
| Centrally managed (FloxHub) | FloxHub, with a cached local copy | flox push on a path environment | https://flox.dev/docs/concepts/environments |
| Remote use | The cached copy of a FloxHub environment | flox activate -r owner/name | https://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
- Do you want the environment to live in the project repo (path environment) or be shared across projects (FloxHub environment)? (https://flox.dev/docs/concepts/environments)
- Is any machine air-gapped or behind a proxy? The hosts Flox needs are in a table at https://flox.dev/docs/concepts/floxhub.
- Do you need telemetry off fleet-wide? Set it in
/etc/flox.tomland your shell profile, and block the endpoints if you need enforcement (https://flox.dev/docs/concepts/data-collection).