Environments and the manifest
A Flox environment is a directory with a TOML manifest, a lockfile and a name. Everything a customer can configure lives in about a dozen manifest sections: install, pkg-groups, vars, hook, profile, services, include, build, options and containerize. Know what each one does, how they merge when environments are composed, and what to commit to git.
This page is drawn from https://flox.dev/docs/concepts/environments, https://flox.dev/docs/man/manifest.toml, https://flox.dev/docs/concepts/activation, https://flox.dev/docs/concepts/composition, https://flox.dev/docs/concepts/package-groups, https://flox.dev/docs/man/flox-containerize, https://flox.dev/docs/tutorials/sharing-environments, https://flox.dev/blog/a-very-short-introduction-to-layering-in-flox/ and https://flox.dev/blog/layering-and-composing-flox-environments/.
Environment types
| Type | Where it lives | How you make or use it | Source |
|---|---|---|---|
| Path environment | A local directory, self contained in .flox | flox init, then --dir/-d on most commands | https://flox.dev/docs/concepts/environments |
| Centrally managed environment | FloxHub | flox push on a path environment; flox pull into a directory; flox activate -r for instant use | https://flox.dev/docs/concepts/environments |
Path environments suit self-contained projects or subprojects within a monorepo, and centrally managed ones suit base environments for similar tech stacks and for reproducing issues on specific systems (https://flox.dev/docs/concepts/environments). flox pull --copy disconnects an environment from FloxHub and turns it back into a path environment (https://flox.dev/docs/concepts/environments). A default environment in $HOME is a common pattern, and you can layer a project environment on top of it (https://flox.dev/docs/tutorials/layering-multiple-environments).
The manifest, section by section
Manifests are TOML. Edit with flox edit, which validates on save (https://flox.dev/docs/concepts/environments). Top-level fields are schema-version, minimum-cli-version and description (https://flox.dev/docs/man/manifest.toml). schema-version names the minimum Flox version the manifest needs; for example 1.15.0 introduced hook.on-deactivate and 1.19.0 introduced options.stability and pkg-groups (https://flox.dev/docs/man/manifest.toml).
install
[install]
ripgrep.pkg-path = "ripgrep"
pip.pkg-path = "python310Packages.pip"
The key is the install ID; only pkg-path is required. Other options are pkg-group, version (exact or semver range), systems, priority (default 5, lower wins file conflicts) and outputs (https://flox.dev/docs/man/manifest.toml). Besides catalog packages, a descriptor can name a Nix flake or a store path; the docs call store paths “most suitable for local experiments” (https://flox.dev/docs/man/manifest.toml).
pkg-groups
[install]
curl.pkg-path = "curl"
python3.pkg-path = "python3"
python3.pkg-group = "legacy"
[pkg-groups]
legacy.stability = "lts"
Everything without a pkg-group is in toplevel. Stability options are unstable (daily), staging (weekly), stable (monthly) and lts (every 6 months), and the default is currently unstable (https://flox.dev/docs/man/manifest.toml). Reach for a group when two packages need version-incompatible sets: requesting old versions of both openssl and curl in one group fails with “constraints for group ‘toplevel’ are too tight” (https://flox.dev/docs/concepts/package-groups).
vars
[vars]
SERVER_PORT = "3000"
BASE_URL = "http://localhost:${SERVER_PORT}"
Variables are set at activation, before hooks and profile. ${NAME} references need Flox CLI 1.14.1 or later (https://flox.dev/docs/man/manifest.toml).
hook
[hook]
on-activate = """
echo "Starting $FLOX_ENV_DESCRIPTION"
"""
on-deactivate = """
rm -rf "$venv_dir"
"""
on-activate is sourced by Bash, runs once per activation rather than once per shell, and should be idempotent. on-deactivate runs in a background process when the last attachment detaches, is best-effort, and cannot prompt (https://flox.dev/docs/man/manifest.toml).
profile
[profile]
common = "echo common"
bash = "alias foo='echo bar'"
zsh = "alias foo='echo bar'"
Profile scripts are sourced by your shell. common runs for every shell, then one of bash, fish, tcsh or zsh runs (https://flox.dev/docs/man/manifest.toml). Keep common portable. Rule of thumb from the activation page: put plain values in [vars], environment-wide setup in hook.on-activate, and aliases and shell-specific things in [profile] (https://flox.dev/docs/concepts/activation).
services
[services.database]
command = "postgres start"
vars.PGPORT = "9001"
[services.web]
command = "run-web"
depends-on.database = { condition = "process_started" }
Services use the packages and variables the environment defines (https://flox.dev/docs/man/manifest.toml). Per-service options are command, vars, is-daemon, shutdown and depends-on, plus systems. Daemonizing commands need both is-daemon and shutdown.command or they keep running after stop (https://flox.dev/docs/man/manifest.toml). auto-start starts all services on flox activate and --no-start-services suppresses it (https://flox.dev/docs/man/manifest.toml).
include
[include]
environments = [
{ dir = "../myenv" },
{ remote = "myuser/myenv" },
]
See the next section. Included environments are not refreshed on their own; flox include upgrade pulls in changes (https://flox.dev/docs/man/manifest.toml).
build
[build.hello]
command = '''
mkdir -p $out
echo 'Hello, World!' >> $out/hello.txt
'''
flox build uses the packages in [install]. Options: sandbox (off, warn, enforce, pure), sandbox-allow, version, description and runtime-packages; “pure” builds need the project under git (https://flox.dev/docs/man/manifest.toml). runtime-packages trims what ships as runtime dependencies (https://flox.dev/docs/man/manifest.toml).
options
[options]
systems = ["aarch64-darwin", "x86_64-linux"]
allow.unfree = true
activate.mode = "run"
Fields: systems, activate.mode, activate.upgrade-notifications, allow.unfree, allow.broken, allow.licenses, semver.allow-pre-releases, cuda-detection (default true) and stability (https://flox.dev/docs/man/manifest.toml). Valid systems are x86_64-linux, aarch64-linux, x86_64-darwin and aarch64-darwin; flox init fills in the current one, and someone pulling an environment that does not list their system is asked whether to add it (https://flox.dev/docs/man/manifest.toml).
containerize
[containerize.config]
exposed-ports = ["8080/tcp"]
cmd = ["./serve"]
working-dir = "/app"
Used by flox containerize. The docs warn containerize.config “is experimental” and its behavior may change; supported keys are user, exposed-ports, cmd, volumes, working-dir, labels and stop-signal (https://flox.dev/docs/man/flox-containerize). The container behaves like flox activate: interactive runs give a subshell, non-interactive runs behave like flox activate -- (https://flox.dev/docs/man/flox-containerize). [plugins] also exists and is marked experimental (https://flox.dev/docs/man/manifest.toml).
Activation modes
options.activate.mode is dev (the default) or run, and flox activate --mode overrides it (https://flox.dev/docs/man/manifest.toml). In dev mode a package, all of its development dependencies, and language specific environment variables are available; in run mode only the requested packages go on PATH, closer to apt or brew (https://flox.dev/docs/man/manifest.toml). The docs warn dev mode may cause unexpected failures when layering environments or when activating an environment system-wide (https://flox.dev/docs/man/manifest.toml). Separately, the invocation style matters: flox activate -- skips profile scripts (https://flox.dev/docs/concepts/activation).
Layering versus composition
| Layering | Composition | |
|---|---|---|
| What it is | Stacking activations, e.g. flox activate -r a -- flox activate -r b | [include] merges manifests, then locks and builds the result |
| When it happens | At runtime, imperatively | When you save the manifest, declaratively |
| Conflicts surface | At runtime | At creation time |
| Shareable as | A documented command sequence | One versioned composing environment |
| Source | https://flox.dev/blog/layering-and-composing-flox-environments/ | https://flox.dev/docs/concepts/composition |
The layering blog summarizes it as layering adding tools dynamically at runtime and composition merging environments declaratively at build (https://flox.dev/blog/layering-and-composing-flox-environments/) In a layered stack the prompt shows the order, such as flox [1p node22 default] (https://flox.dev/blog/a-very-short-introduction-to-layering-in-flox/). The same blog names the downsides of layering: order decides precedence, and upper layers can hide or alter lower ones (https://flox.dev/blog/layering-and-composing-flox-environments/).
How composition merges (https://flox.dev/docs/man/manifest.toml, https://flox.dev/docs/concepts/composition):
| Section | Rule |
|---|---|
| install, vars, services | Higher priority replaces the whole entry |
| hook, profile | Scripts appended, higher priority last |
| options | Deep merged, field by field |
| containerize | user, cmd, working-dir, stop-signal replaced; labels and ports merged |
| description | Only the composing environment’s is used |
Later entries in include.environments beat earlier ones, and the composing manifest wins over all of them (https://flox.dev/docs/man/manifest.toml). There is no way to remove something from a lower-priority manifest (https://flox.dev/docs/man/manifest.toml). flox list --config shows the merged manifest (https://flox.dev/docs/man/manifest.toml). One trap: if any included environment sets options.activate.mode = "run", the merged manifest is in run mode unless a higher manifest sets dev (https://flox.dev/docs/man/manifest.toml). Another: you can push a composed environment to FloxHub only if it includes no local dir environments (https://flox.dev/docs/concepts/composition).
Say: “Layer when you need a tool right now. Compose when the combination is a product you will version and hand to a team.” Ask: “Does anyone else need to reproduce this exact stack?” If yes, compose.
Package groups in one paragraph
A package group is a set of packages resolved against one nixpkgs commit; groups are upgraded as a unit, and different groups may resolve to different commits (https://flox.dev/docs/man/manifest.toml, https://flox.dev/docs/concepts/package-groups). Groups also work as labels, for example to keep dev-only tools out of a build’s runtime closure (https://flox.dev/docs/concepts/package-groups).
What to commit
The .flox directory “contains everything required to run this environment on their system”, and “It is most common to commit the .flox to a version controlled code repository such as git” (https://flox.dev/docs/tutorials/sharing-environments). A teammate then only needs git clone and flox activate (https://flox.dev/docs/tutorials/sharing-environments). The docs show git add .flox && git commit (https://flox.dev/docs/tutorials/sharing-environments), and manifest.toml, manifest.lock and .flox/pkgs are all intended to be versioned (https://flox.dev/docs/concepts/environments). Which files inside .flox (for example run, cache and log) a generated ignore file excludes is not stated in the pages read [verify]. $FLOX_ENV_CACHE content is not pushed to FloxHub (https://flox.dev/docs/man/flox-activate). Anything in [vars] or a hook is stored in plain text on FloxHub when pushed, and pushed environments are public by default (https://flox.dev/docs/concepts/data-collection), so keep secrets out of the manifest.