Manual

Requirements and environments

What a script needs from Galaxy and from the machine that runs it, and three ways to have it: an environment built with uv, a container, or a community base.

Galaxy requirements#

requires_role("geerlingguy.postgresql", version="3.5.2")
requires_collection("community.general", version=">=8.0.0")

They become requirements.yml, and install (or run --install) puts them in roles/ and collections/ inside the generated tree, where ansible finds them without any configuration. That is the only place they go: an environment (below) never holds them.

The tree’s ansible.cfg, yours or instamix’s, gets collections_path = ./collections and roles_path = ./roles when it sets none, so galaxy content stays in the tree (the ansible-cfg.local-paths setting; on by default). install goes where the first of each points, and refuses to run when that is outside the tree. It counts what the environment in use has, so a dependency the environment holds isn’t installed again, but not what the host has installed.

When ansible runs, that ansible.cfg alone says where it searches: ANSIBLE_COLLECTIONS_PATH and ANSIBLE_ROLES_PATH, your own included, are left out of its environment once the file names the paths, so ~/.ansible and the system directories only count if it says so.

Runtime requirements#

What the machine that runs the playbook needs:

requires_ansible(">=2.16,<2.19")        # ansible-core
requires_python(">=3.11")               # the Python ansible runs on
requires_pip("jmespath", "netaddr>=0.10")
requires_command("rsync", "ssh")

doctor checks them against this machine. run, install, test and lint refuse to start when one is missing, and name it; --skip-requirements runs anyway. Versions and packages are looked up in the Python that ansible-playbook runs on, which is not necessarily the one running the script. Pip requirements also go into a requirements.txt, unless you declare one. Like everything else, they respect profiles and only=.

Environments#

env builds that controller with uv: a virtualenv on the Python requires_python() asks for, holding ansible-core and the pip requirements, resolved into a lock with hashes and installed from it with --require-hashes. It prints the directory.

$ python site.py env
~/.cache/instamix/envs/af699b99d7fec240
instamix: built ansible-core 2.17.14 on Python 3.12.13
$ python site.py run --env      # also install, lint, test and doctor

--env (or INSTAMIX_ENV=1) builds the environment if needed and puts its bin/ first on PATH for everything instamix starts; without it, ansible comes from PATH as before.

Environments live under ~/.cache/instamix/envs/, named by a digest of what they hold, so scripts with the same requirements share one and a change in them makes a new one. env --dir DIR builds somewhere else; env --upgrade resolves again instead of reusing the lock. Each holds venv/, requirements.in, requirements.lock and env.json. requires_command() is only checked: uv cannot provide programs outside Python.

An environment holds galaxy content only when a base (below) brings it: a script’s own collections and roles go into its tree. A base’s go in with the environment’s own ansible-galaxy, into its collections/ and roles/, through an ansible.cfg of its own: what the host has installed, and the host’s ansible.cfg, don’t count.

When a tree is built for an environment with --outside-paths (run --env --outside-paths, off by default), its ansible.cfg also names the environment’s collections/ and roles/ in collections_path and roles_path, after the tree’s own (or before ansible’s defaults, with ansible-cfg.local-paths off), so ansible-playbook run in the tree by hand finds them, and --install doesn’t install what the environment has again. Without it, only the tree counts: everything the playbook needs goes into the tree.

Containers#

imix container SCRIPT [--image IMAGE] [OPTIONS] [PLAYBOOK] [-- ARGS] (also python site.py container ...) runs the playbook in a container instead: the tree is built here and mounted at /runner/project, and only ansible runs inside – ansible-galaxy for --install, then ansible-playbook – so any execution environment image will do. It takes run’s options.

The image is --image, else the script’s container_image("..."), else its base’s (requires_base("base") means ghcr.io/ansible-community/community-ee-base:latest); python site.py image prints which, and why. The community images are meant for development and CI, not production.

The container gets the host’s network (hosts and the proxy are reached as from here), the SSH agent, ~/.ssh read-only, and, read-only, the files outside the tree that -i (with the group_vars/ and host_vars/ next to it), --vault-password-file and -e @FILE name; those arguments are rewritten to where the container sees them. Paths after -- are passed as they are.

It runs with podman, else docker (--engine picks one); with podman the image’s user is you (--userns=keep-id), and SELinux labelling is off for it so ~/.ssh is readable without relabelling. The tree’s ansible.cfg also names the image’s own collections and roles, after the tree’s, so what the image ships counts and isn’t installed again (--outside-paths, on by default here; --no-outside-paths for a tree that holds everything itself).

Its options are its own, so settings for it go in a [container] section: install = true there doesn’t come from [run]. --pull says when to pull the image (default: when missing).

Bases#

requires_base("minimal") or requires_base("base") starts the environment from what the community execution environments (ansible-community/images) put on the controller: their Python, their packages as they pin them (ansible-core among them), and their collections (base: ansible.posix, ansible.utils and ansible.windows; minimal: none).

The environment then holds the base alone, so scripts on the same base share one; a script’s own requires_collection() and requires_role() go into its tree (run --install), as always. requires_ansible() and requires_python() narrow the base’s pins, and requires_pip() adds to them.

requires_base("base")
requires_collection("community.general")   # in the tree, with --install

The pins live in instamix_bases.py; tools/update_bases.py [--ref TAG] writes it again from the images repository.

The source of this page

    Type to search. ↑ ↓ to move, enter to open.