Skip to main content
RunBook Academy

AnsibleLII · Anti-PatternsAnti-patterns of neglect

Anti-pattern: latest everywhere

Intermediate⏱ ~26 minbash

What you'll learn

  • Identify the four layers where "latest" silently enters an Ansible estate
  • Explain why an unpinned estate makes convergence date-dependent
  • Connect the anti-pattern to the automation DR failure it guarantees
  • Adopt pinning without freezing the estate permanently

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

Configuration management exists to make hosts the same. This anti-pattern makes what “the same” means depend on the date the host was converged.

It enters at four layers, and most estates have it at all four without anyone having chosen it.

Layer 1: state: latest in roles

Service impact possiblethe task, and what it actually means
- name: Install nginx
ansible.builtin.package:
  name: nginx
  state: latest
notify: Restart nginx

state: latest does not mean “install it”. It means “upgrade it to whatever the repository offers, right now”.

The consequences arrive as a set:

  • The converged state is not defined by the repository. Two hosts converged a week apart hold different versions. Both runs succeeded and both reported convergence.
  • Every run is a potential upgrade. A play intended to fix a config typo can also bump nginx across three hundred hosts, because the task was in the same role.
  • The handler fires on the upgrade. So the service restarts, on a run nobody expected to restart anything.
  • The change is unannounced. No ticket, no window, no review — the upstream maintainer’s release schedule is now your change schedule.

The corrected form is state: present for “ensure it is installed”, and an explicit version where the version matters:

Configuration changepresent, and pinned where it matters
- name: Install nginx
ansible.builtin.package:
  name: nginx
  state: present

- name: Install the application at the version this release deploys
ansible.builtin.package:
  name: 'app-server={{ app_server_version }}'
  state: present

Upgrading then becomes a deliberate act: change app_server_version in a commit, review it, and roll it out with the staging from lesson 3. Part XL covers patching as its own scheduled activity, which is where upgrades belong.

Layer 2: unpinned collections

Read-only / Safethree requirements.yml files, only one of which pins
# Unpinned - installs whatever is current today.
collections:
- name: community.general

# A range - narrower, and still not reproducible.
collections:
- name: community.general
  version: '>=12.0.0,<13.0.0'

# Pinned - the same artefact every time.
collections:
- name: community.general
  version: 12.0.0

The failure. A collection release changes a module’s default, adds a parameter, or fixes a bug you were depending on. Nothing in your repository changed, and the fleet’s behaviour did.

The version that makes this concrete: a module gains stricter validation in a minor release, and a playbook that has run nightly for a year starts failing at 02:00 with an error nobody has seen. Your first hypothesis will be that something changed on the hosts. Nothing did.

Layer 3: no requirements.yml at all

The version of layer 2 where there is nothing to unpin. Collections were installed by hand, on the controller, over several years.

The failure is the one Part L is built on. The controller is lost, or a second one is built, and there is no definition to install from. The collection set is unrecoverable knowledge, and the rebuild produces a controller that works until the first play that needs the collection somebody installed during an incident in March.

Layer 4: no controller pinning

ansible-core and its Python dependencies installed from whatever the distribution or PyPI currently offers.

The failure is a behaviour difference between controllers, or across time on the same controller. ansible-core minor releases deprecate keywords, change module behaviour and occasionally alter defaults. The porting guides exist precisely because these changes are real.

The estate this produces

  1. Hosts differ by their build date, so "which version of nginx is on the fleet" has as many answers as there were provisioning waves.
  2. Reproducing a bug in staging fails, because staging was built last month and production was built last year.
  3. A rollback target cannot be named. "The previous version" is a different string on different hosts.
  4. Incident timelines have an unexplained variable: something changed, and the change is not in any repository you control.
  5. Compliance evidence is weak. "The estate runs approved versions" is unprovable if the versions were never declared.

This is the snowflake problem from Part I arriving through a different door. Part I described snowflakes made by hand; this is the same divergence produced by automation that ran correctly.

Finding it

Read-only / Safeaudit the four layers
cd /srv/ansible

# Layer 1
grep -rn 'state:\s*latest' --include='*.yml' --include='*.yaml' .

# Layer 2 and 3
test -f requirements.yml && cat requirements.yml || echo 'NO requirements.yml'
ansible-galaxy collection list

# Layer 4
ansible --version | head -1
pip --disable-pip-version-check list 2>/dev/null | grep -i ansible

# The whole picture: what is actually installed versus what is declared.
ansible-lint --profile production

The comparison that matters is between ansible-galaxy collection list and requirements.yml. Every installed collection missing from the declaration is a rebuild failure that has not happened yet.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A role uses `state: latest` for nginx and notifies a restart handler. An engineer runs the play to fix a typo in an unrelated config file. What happens?

  2. Q2. Which of these make a controller rebuild fail to reproduce the original environment? Select all that apply.

  3. Q3. With unpinned collections, a fleet behaviour change can occur with no commit anywhere in your repository.

  4. Q4. A team objects that pinning will leave the estate unpatched. What is the correct response?

Passing score: 75%. Answers are checked in this browser.