nixfredFLOXFIELD GUIDE · BY NIX UNOFFICIAL
LEARN / ENVIRONMENTS AND THE MANIFEST

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

TypeWhere it livesHow you make or use itSource
Path environmentA local directory, self contained in .floxflox init, then --dir/-d on most commandshttps://flox.dev/docs/concepts/environments
Centrally managed environmentFloxHubflox push on a path environment; flox pull into a directory; flox activate -r for instant usehttps://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

LayeringComposition
What it isStacking activations, e.g. flox activate -r a -- flox activate -r b[include] merges manifests, then locks and builds the result
When it happensAt runtime, imperativelyWhen you save the manifest, declaratively
Conflicts surfaceAt runtimeAt creation time
Shareable asA documented command sequenceOne versioned composing environment
Sourcehttps://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):

SectionRule
install, vars, servicesHigher priority replaces the whole entry
hook, profileScripts appended, higher priority last
optionsDeep merged, field by field
containerizeuser, cmd, working-dir, stop-signal replaced; labels and ports merged
descriptionOnly 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.

← The architecture · The Catalog, builds and publishing →